diff --git a/doc/fieldparams.md b/doc/fieldparams.md index c098151c..8d33d6c4 100644 --- a/doc/fieldparams.md +++ b/doc/fieldparams.md @@ -120,7 +120,7 @@ implementation detail by client code. It automatically propagates through the field resolvers, and if there is a schema exception or one of the `getField` accessors throws another exception derived from `std::exception`, the `graphqlservice` library will automatically add the resulting path to the error -report, accoring to the [spec](http://spec.graphql.org/June2018/#sec-Errors). +report, accoring to the [spec](https://spec.graphql.org/October2021/#sec-Errors). ### Launch Policy diff --git a/doc/responses.md b/doc/responses.md index 6c4d9cb1..a1cf432b 100644 --- a/doc/responses.md +++ b/doc/responses.md @@ -7,7 +7,7 @@ As the comment in responses are not technically JSON-specific, although that is probably the most common way of representing them. These are the primitive types that may be represented in GraphQL, as of the -[June 2018 spec](http://spec.graphql.org/June2018/#sec-Serialization-Format): +[October 2021 spec](https://spec.graphql.org/October2021/#sec-Serialization-Format): ```c++ enum class Type : uint8_t diff --git a/include/SchemaGenerator.h b/include/SchemaGenerator.h index 00ac5b53..58ced48f 100644 --- a/include/SchemaGenerator.h +++ b/include/SchemaGenerator.h @@ -57,6 +57,8 @@ class Generator void outputObjectImplementation( std::ostream& sourceFile, const ObjectType& objectType, bool isQueryType) const; void outputObjectIntrospection(std::ostream& sourceFile, const ObjectType& objectType) const; + void outputIntrospectionInterfaces(std::ostream& sourceFile, std::string_view cppType, + const std::vector& interfaces) const; void outputIntrospectionFields( std::ostream& sourceFile, std::string_view cppType, const OutputFieldList& fields) const; std::string getArgumentDefaultValue( diff --git a/include/SchemaLoader.h b/include/SchemaLoader.h index fe7def07..1cae7d2d 100644 --- a/include/SchemaLoader.h +++ b/include/SchemaLoader.h @@ -48,6 +48,7 @@ struct ScalarType { std::string_view type; std::string_view description; + std::string_view specifiedByURL; }; using ScalarTypeList = std::vector; @@ -113,6 +114,7 @@ using InputTypeList = std::vector; struct Directive { std::string_view name; + bool isRepeatable = false; std::vector locations; InputFieldList arguments; std::string_view description; @@ -174,6 +176,7 @@ struct InterfaceType { std::string_view type; std::string_view cppType; + std::vector interfaces; OutputFieldList fields; std::string_view description; }; @@ -219,6 +222,7 @@ class SchemaLoader explicit SchemaLoader(SchemaOptions&& schemaOptions); bool isIntrospection() const noexcept; + std::string_view getSchemaDescription() const noexcept; std::string_view getFilenamePrefix() const noexcept; std::string_view getSchemaNamespace() const noexcept; @@ -265,6 +269,7 @@ class SchemaLoader void visitSchemaDefinition(const peg::ast_node& schemaDefinition); void visitSchemaExtension(const peg::ast_node& schemaExtension); void visitScalarTypeDefinition(const peg::ast_node& scalarTypeDefinition); + void visitScalarTypeExtension(const peg::ast_node& scalarTypeExtension); void visitEnumTypeDefinition(const peg::ast_node& enumTypeDefinition); void visitEnumTypeExtension(const peg::ast_node& enumTypeExtension); void visitInputObjectTypeDefinition(const peg::ast_node& inputObjectTypeDefinition); @@ -277,6 +282,8 @@ class SchemaLoader void visitObjectTypeExtension(const peg::ast_node& objectTypeExtension); void visitDirectiveDefinition(const peg::ast_node& directiveDefinition); + static void blockReservedName( + std::string_view name, std::optional position = std::nullopt); static OutputFieldList getOutputFields(const peg::ast_node::children_t& fields); static InputFieldList getInputFields(const peg::ast_node::children_t& fields); @@ -286,6 +293,13 @@ class SchemaLoader const std::optional& accessor); void fixupInputFieldList(InputFieldList& fields); void reorderInputTypeDependencies(); + void validateImplementedInterfaces() const; + const InterfaceType& findInterfaceType( + std::string_view typeName, std::string_view interfaceName) const; + void validateInterfaceFields(std::string_view typeName, + std::string_view interfaceName, const OutputFieldList& typeFields) const; + void validateTransitiveInterfaces( + std::string_view typeName, const std::vector& interfaces) const; static const std::string_view s_introspectionNamespace; static const BuiltinTypeMap s_builtinTypes; @@ -294,6 +308,7 @@ class SchemaLoader const SchemaOptions _schemaOptions; const bool _isIntrospection; + std::string_view _schemaDescription; std::string_view _schemaNamespace; peg::ast _ast; diff --git a/include/Validation.h b/include/Validation.h index b109cb7c..f9a11f3c 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -37,6 +37,7 @@ using ValidateDirectiveArguments = internal::string_view_map; struct ValidateDirective { + bool isRepeatable = false; internal::sorted_set locations; ValidateDirectiveArguments arguments; }; @@ -250,6 +251,7 @@ class ValidateExecutableVisitor VariableSet _referencedVariables; FragmentSet _fragmentStack; size_t _fieldCount = 0; + size_t _introspectionFieldCount = 0; TypeFields _typeFields; InputTypeFields _inputTypeFields; ValidateType _scopedType; diff --git a/include/graphqlservice/GraphQLResponse.h b/include/graphqlservice/GraphQLResponse.h index 6681b7b0..f50774b2 100644 --- a/include/graphqlservice/GraphQLResponse.h +++ b/include/graphqlservice/GraphQLResponse.h @@ -30,7 +30,7 @@ namespace graphql::response { // GraphQL responses are not technically JSON-specific, although that is probably the most common // way of representing them. These are the primitive types that may be represented in GraphQL, as -// of the [June 2018 spec](http://spec.graphql.org/June2018/#sec-Serialization-Format). +// of the [October 2021 spec](https://spec.graphql.org/October2021/#sec-Serialization-Format). enum class Type : uint8_t { Map, // JSON Object diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 79d71012..0ab47207 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -229,6 +229,15 @@ class await_async : public coro::suspend_always } }; +// Directive order matters, and some of them are repeatable. So rather than passing them in a +// response::Value, pass directives in something like the underlying response::MapType which +// preserves the order of the elements without complete uniqueness. +using Directives = std::vector>; + +// Traversing a fragment spread adds a new set of directives. +using FragmentDefinitionDirectiveStack = std::list>; +using FragmentSpreadDirectiveStack = std::list; + // Pass a common bundle of parameters to all of the generated Object::getField accessors in a // SelectionSet struct SelectionSetParams @@ -239,14 +248,14 @@ struct SelectionSetParams // The lifetime of each of these borrowed references is guaranteed until the future returned // by the accessor is resolved or destroyed. They are owned by the OperationData shared pointer. const std::shared_ptr& state; - const response::Value& operationDirectives; - const response::Value& fragmentDefinitionDirectives; + const Directives& operationDirectives; + const std::shared_ptr fragmentDefinitionDirectives; // Fragment directives are shared for all fields in that fragment, but they aren't kept alive // after the call to the last accessor in the fragment. If you need to keep them alive longer, - // you'll need to explicitly copy them into other instances of response::Value. - const response::Value& fragmentSpreadDirectives; - const response::Value& inlineFragmentDirectives; + // you'll need to explicitly copy them into other instances of Directives. + const std::shared_ptr fragmentSpreadDirectives; + const std::shared_ptr inlineFragmentDirectives; // Field error path to this selection set. std::optional errorPath; @@ -259,12 +268,12 @@ struct SelectionSetParams struct FieldParams : SelectionSetParams { GRAPHQLSERVICE_EXPORT explicit FieldParams( - SelectionSetParams&& selectionSetParams, response::Value directives); + SelectionSetParams&& selectionSetParams, Directives directives); // Each field owns its own field-specific directives. Once the accessor returns it will be // destroyed, but you can move it into another instance of response::Value to keep it alive // longer. - response::Value fieldDirectives; + Directives fieldDirectives; }; // Field accessors may return either a result of T, an awaitable of T, or a std::future, so at @@ -379,11 +388,11 @@ class Fragment std::string_view getType() const; const peg::ast_node& getSelection() const; - const response::Value& getDirectives() const; + const Directives& getDirectives() const; private: std::string_view _type; - response::Value _directives; + Directives _directives; std::reference_wrapper _selection; }; @@ -399,8 +408,8 @@ struct ResolverParams : SelectionSetParams { GRAPHQLSERVICE_EXPORT explicit ResolverParams(const SelectionSetParams& selectionSetParams, const peg::ast_node& field, std::string&& fieldName, response::Value arguments, - response::Value fieldDirectives, const peg::ast_node* selection, - const FragmentMap& fragments, const response::Value& variables); + Directives fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, + const response::Value& variables); GRAPHQLSERVICE_EXPORT schema_location getLocation() const; @@ -408,7 +417,7 @@ struct ResolverParams : SelectionSetParams const peg::ast_node& field; std::string fieldName; response::Value arguments { response::Type::Map }; - response::Value fieldDirectives { response::Type::Map }; + Directives fieldDirectives; const peg::ast_node* selection; // These values remain unchanged for the entire operation, but they're passed to each of the @@ -944,11 +953,11 @@ struct SubscriptionParams struct OperationData : std::enable_shared_from_this { explicit OperationData(std::shared_ptr state, response::Value variables, - response::Value directives, FragmentMap fragments); + Directives directives, FragmentMap fragments); std::shared_ptr state; response::Value variables; - response::Value directives; + Directives directives; FragmentMap fragments; }; @@ -956,7 +965,8 @@ struct OperationData : std::enable_shared_from_this // SelectionSet against the payload. using SubscriptionCallback = std::function; using SubscriptionArguments = std::map; -using SubscriptionFilterCallback = std::function; +using SubscriptionArgumentFilterCallback = std::function; +using SubscriptionDirectiveFilterCallback = std::function; // Subscriptions are stored in maps using these keys. using SubscriptionKey = size_t; @@ -970,7 +980,7 @@ using AwaitableDeliver = internal::Awaitable; struct SubscriptionData : std::enable_shared_from_this { explicit SubscriptionData(std::shared_ptr data, SubscriptionName&& field, - response::Value arguments, response::Value fieldDirectives, peg::ast&& query, + response::Value arguments, Directives fieldDirectives, peg::ast&& query, std::string&& operationName, SubscriptionCallback&& callback, const peg::ast_node& selection); @@ -978,7 +988,7 @@ struct SubscriptionData : std::enable_shared_from_this SubscriptionName field; response::Value arguments; - response::Value fieldDirectives; + Directives fieldDirectives; peg::ast query; std::string operationName; SubscriptionCallback callback; @@ -1023,14 +1033,14 @@ class Request : public std::enable_shared_from_this GRAPHQLSERVICE_EXPORT void deliver(const SubscriptionName& name, const SubscriptionArguments& arguments, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT void deliver(const SubscriptionName& name, - const SubscriptionArguments& arguments, const SubscriptionArguments& directives, + const SubscriptionArguments& arguments, const Directives& directives, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT void deliver(const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, + const SubscriptionArgumentFilterCallback& applyArguments, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT void deliver(const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, - const SubscriptionFilterCallback& applyDirectives, + const SubscriptionArgumentFilterCallback& applyArguments, + const SubscriptionDirectiveFilterCallback& applyDirectives, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT AwaitableDeliver deliver(await_async launch, const SubscriptionName& name, @@ -1038,14 +1048,14 @@ class Request : public std::enable_shared_from_this GRAPHQLSERVICE_EXPORT AwaitableDeliver deliver(await_async launch, const SubscriptionName& name, const SubscriptionArguments& arguments, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT AwaitableDeliver deliver(await_async launch, const SubscriptionName& name, - const SubscriptionArguments& arguments, const SubscriptionArguments& directives, + const SubscriptionArguments& arguments, const Directives& directives, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT AwaitableDeliver deliver(await_async launch, const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, + const SubscriptionArgumentFilterCallback& applyArguments, std::shared_ptr subscriptionObject) const; GRAPHQLSERVICE_EXPORT AwaitableDeliver deliver(await_async launch, const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, - const SubscriptionFilterCallback& applyDirectives, + const SubscriptionArgumentFilterCallback& applyArguments, + const SubscriptionDirectiveFilterCallback& applyDirectives, std::shared_ptr subscriptionObject) const; private: diff --git a/include/graphqlservice/internal/Grammar.h b/include/graphqlservice/internal/Grammar.h index 1e95e2d3..f1f3d619 100644 --- a/include/graphqlservice/internal/Grammar.h +++ b/include/graphqlservice/internal/Grammar.h @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // -// This grammar is based on the June 2018 Edition of the GraphQL spec: -// http://spec.graphql.org/June2018/ +// This grammar is based on the October 2021 Edition of the GraphQL spec: +// https://spec.graphql.org/October2021/ #pragma once @@ -59,22 +59,22 @@ void on_first_child(const ast_node& n, std::function&& fu } } -// http://spec.graphql.org/June2018/#sec-Source-Text +// https://spec.graphql.org/October2021/#sec-Source-Text struct source_character : sor, utf8::range<0x0020, 0xFFFF>> { }; -// http://spec.graphql.org/June2018/#sec-Comments +// https://spec.graphql.org/October2021/#sec-Comments struct comment : seq, until> { }; -// http://spec.graphql.org/June2018/#sec-Source-Text.Ignored-Tokens +// https://spec.graphql.org/October2021/#sec-Source-Text.Ignored-Tokens struct ignored : sor, comment> { }; -// http://spec.graphql.org/June2018/#sec-Names +// https://spec.graphql.org/October2021/#sec-Names struct name : identifier { }; @@ -83,12 +83,12 @@ struct variable_name_content : name { }; -// http://spec.graphql.org/June2018/#Variable +// https://spec.graphql.org/October2021/#Variable struct variable_name : if_must, variable_name_content> { }; -// http://spec.graphql.org/June2018/#sec-Null-Value +// https://spec.graphql.org/October2021/#sec-Null-Value struct null_keyword : TAO_PEGTL_KEYWORD("null") { }; @@ -109,12 +109,12 @@ struct escaped_unicode_content : list, escaped_unicode_content> { }; -// http://spec.graphql.org/June2018/#EscapedCharacter +// https://spec.graphql.org/October2021/#EscapedCharacter struct escaped_char : one<'"', '\\', '/', 'b', 'f', 'n', 'r', 't'> { }; @@ -137,7 +137,7 @@ struct string_quote_content { }; -// http://spec.graphql.org/June2018/#StringCharacter +// https://spec.graphql.org/October2021/#StringCharacter struct string_quote : if_must { }; @@ -151,26 +151,42 @@ struct block_escape_sequence : seq }; struct block_quote_character - : plus, not_at, source_character> + : plus, not_at, not_at, + source_character> { }; -struct block_quote_content - : seq>, must> +struct block_quote_empty_line : star, space> { }; -// http://spec.graphql.org/June2018/#BlockStringCharacter +struct block_quote_line_content : plus> +{ +}; + +struct block_quote_line : seq +{ +}; + +struct block_quote_content_lines : opt, eol>> +{ +}; + +struct block_quote_content : seq> +{ +}; + +// https://spec.graphql.org/October2021/#BlockStringCharacter struct block_quote : if_must { }; -// http://spec.graphql.org/June2018/#StringValue +// https://spec.graphql.org/October2021/#StringValue struct string_value : sor { }; -// http://spec.graphql.org/June2018/#NonZeroDigit +// https://spec.graphql.org/October2021/#NonZeroDigit struct nonzero_digit : range<'1', '9'> { }; @@ -179,17 +195,17 @@ struct zero_digit : one<'0'> { }; -// http://spec.graphql.org/June2018/#NegativeSign +// https://spec.graphql.org/October2021/#NegativeSign struct negative_sign : one<'-'> { }; -// http://spec.graphql.org/June2018/#IntegerPart +// https://spec.graphql.org/October2021/#IntegerPart struct integer_part : seq, sor>>> { }; -// http://spec.graphql.org/June2018/#IntValue +// https://spec.graphql.org/October2021/#IntValue struct integer_value : integer_part { }; @@ -198,17 +214,17 @@ struct fractional_part_content : plus { }; -// http://spec.graphql.org/June2018/#FractionalPart +// https://spec.graphql.org/October2021/#FractionalPart struct fractional_part : if_must, fractional_part_content> { }; -// http://spec.graphql.org/June2018/#ExponentIndicator +// https://spec.graphql.org/October2021/#ExponentIndicator struct exponent_indicator : one<'e', 'E'> { }; -// http://spec.graphql.org/June2018/#Sign +// https://spec.graphql.org/October2021/#Sign struct sign : one<'+', '-'> { }; @@ -217,12 +233,12 @@ struct exponent_part_content : seq, plus> { }; -// http://spec.graphql.org/June2018/#ExponentPart +// https://spec.graphql.org/October2021/#ExponentPart struct exponent_part : if_must { }; -// http://spec.graphql.org/June2018/#FloatValue +// https://spec.graphql.org/October2021/#FloatValue struct float_value : seq>> { @@ -236,17 +252,17 @@ struct false_keyword : TAO_PEGTL_KEYWORD("false") { }; -// http://spec.graphql.org/June2018/#BooleanValue +// https://spec.graphql.org/October2021/#BooleanValue struct bool_value : sor { }; -// http://spec.graphql.org/June2018/#EnumValue +// https://spec.graphql.org/October2021/#EnumValue struct enum_value : seq, name> { }; -// http://spec.graphql.org/June2018/#OperationType +// https://spec.graphql.org/October2021/#OperationType struct operation_type : sor @@ -257,7 +273,7 @@ struct alias_name : name { }; -// http://spec.graphql.org/June2018/#Alias +// https://spec.graphql.org/October2021/#Alias struct alias : seq, one<':'>> { }; @@ -272,7 +288,7 @@ struct argument_content : seq, one<':'>, star, input_valu { }; -// http://spec.graphql.org/June2018/#Argument +// https://spec.graphql.org/October2021/#Argument struct argument : if_must { }; @@ -282,7 +298,7 @@ struct arguments_content { }; -// http://spec.graphql.org/June2018/#Arguments +// https://spec.graphql.org/October2021/#Arguments struct arguments : if_must, arguments_content> { }; @@ -294,7 +310,7 @@ struct list_value_content { }; -// http://spec.graphql.org/June2018/#ListValue +// https://spec.graphql.org/October2021/#ListValue struct list_value : if_must, list_value_content> { }; @@ -307,7 +323,7 @@ struct object_field_content : seq, one<':'>, star, input_ { }; -// http://spec.graphql.org/June2018/#ObjectField +// https://spec.graphql.org/October2021/#ObjectField struct object_field : if_must { }; @@ -317,7 +333,7 @@ struct object_value_content { }; -// http://spec.graphql.org/June2018/#ObjectValue +// https://spec.graphql.org/October2021/#ObjectValue struct object_value : if_must, object_value_content> { }; @@ -332,7 +348,7 @@ struct input_value_content { }; -// http://spec.graphql.org/June2018/#Value +// https://spec.graphql.org/October2021/#Value struct input_value : must { }; @@ -345,12 +361,12 @@ struct default_value_content : seq, input_value> { }; -// http://spec.graphql.org/June2018/#DefaultValue +// https://spec.graphql.org/October2021/#DefaultValue struct default_value : if_must, default_value_content> { }; -// http://spec.graphql.org/June2018/#NamedType +// https://spec.graphql.org/October2021/#NamedType struct named_type : name { }; @@ -363,12 +379,12 @@ struct list_type_content { }; -// http://spec.graphql.org/June2018/#ListType +// https://spec.graphql.org/October2021/#ListType struct list_type : if_must, list_type_content> { }; -// http://spec.graphql.org/June2018/#NonNullType +// https://spec.graphql.org/October2021/#NonNullType struct nonnull_type : seq, star, one<'!'>> { }; @@ -377,7 +393,7 @@ struct type_name_content : sor { }; -// http://spec.graphql.org/June2018/#Type +// https://spec.graphql.org/October2021/#Type struct type_name : must { }; @@ -387,7 +403,7 @@ struct variable_content { }; -// http://spec.graphql.org/June2018/#VariableDefinition +// https://spec.graphql.org/October2021/#VariableDefinition struct variable : if_must { }; @@ -397,7 +413,7 @@ struct variable_definitions_content { }; -// http://spec.graphql.org/June2018/#VariableDefinitions +// https://spec.graphql.org/October2021/#VariableDefinitions struct variable_definitions : if_must, variable_definitions_content> { }; @@ -410,12 +426,12 @@ struct directive_content : seq, arguments>> { }; -// http://spec.graphql.org/June2018/#Directive +// https://spec.graphql.org/October2021/#Directive struct directive : if_must, directive_content> { }; -// http://spec.graphql.org/June2018/#Directives +// https://spec.graphql.org/October2021/#Directives struct directives : list> { }; @@ -448,7 +464,7 @@ struct field_content { }; -// http://spec.graphql.org/June2018/#Field +// https://spec.graphql.org/October2021/#Field struct field : if_must { }; @@ -457,7 +473,7 @@ struct on_keyword : TAO_PEGTL_KEYWORD("on") { }; -// http://spec.graphql.org/June2018/#FragmentName +// https://spec.graphql.org/October2021/#FragmentName struct fragment_name : seq, name> { }; @@ -466,7 +482,7 @@ struct fragment_token : ellipsis { }; -// http://spec.graphql.org/June2018/#FragmentSpread +// https://spec.graphql.org/October2021/#FragmentSpread struct fragment_spread : seq, fragment_name, opt, directives>> { }; @@ -475,12 +491,12 @@ struct type_condition_content : seq, named_type> { }; -// http://spec.graphql.org/June2018/#TypeCondition +// https://spec.graphql.org/October2021/#TypeCondition struct type_condition : if_must { }; -// http://spec.graphql.org/June2018/#InlineFragment +// https://spec.graphql.org/October2021/#InlineFragment struct inline_fragment : seq, type_condition>, opt, directives>, star, selection_set> @@ -496,7 +512,7 @@ struct fragement_spread_or_inline_fragment { }; -// http://spec.graphql.org/June2018/#Selection +// https://spec.graphql.org/October2021/#Selection struct selection : sor { }; @@ -506,7 +522,7 @@ struct selection_set_content { }; -// http://spec.graphql.org/June2018/#SelectionSet +// https://spec.graphql.org/October2021/#SelectionSet struct selection_set : if_must, selection_set_content> { }; @@ -521,7 +537,7 @@ struct operation_definition_operation_type_content { }; -// http://spec.graphql.org/June2018/#OperationDefinition +// https://spec.graphql.org/October2021/#OperationDefinition struct operation_definition : sor, selection_set> { @@ -533,16 +549,21 @@ struct fragment_definition_content { }; -// http://spec.graphql.org/June2018/#FragmentDefinition +// https://spec.graphql.org/October2021/#FragmentDefinition struct fragment_definition : if_must { }; -// http://spec.graphql.org/June2018/#ExecutableDefinition +// https://spec.graphql.org/October2021/#ExecutableDefinition struct executable_definition : sor { }; +// https://spec.graphql.org/October2021/#Description +struct description : string_value +{ +}; + struct schema_keyword : TAO_PEGTL_KEYWORD("schema") { }; @@ -551,19 +572,23 @@ struct root_operation_definition_content : seq, one<':'>, star { }; +struct schema_definition_start : seq>, schema_keyword> +{ +}; + struct schema_definition_content : seq, directives>, star, one<'{'>, star, list>, star, must>> { }; -// http://spec.graphql.org/June2018/#SchemaDefinition -struct schema_definition : if_must +// https://spec.graphql.org/October2021/#SchemaDefinition +struct schema_definition : if_must { }; @@ -571,11 +596,6 @@ struct scalar_keyword : TAO_PEGTL_KEYWORD("scalar") { }; -// http://spec.graphql.org/June2018/#Description -struct description : string_value -{ -}; - struct scalar_name : name { }; @@ -589,7 +609,7 @@ struct scalar_type_definition_content { }; -// http://spec.graphql.org/June2018/#ScalarTypeDefinition +// https://spec.graphql.org/October2021/#ScalarTypeDefinition struct scalar_type_definition : if_must { @@ -610,7 +630,7 @@ struct arguments_definition_content { }; -// http://spec.graphql.org/June2018/#ArgumentsDefinition +// https://spec.graphql.org/October2021/#ArgumentsDefinition struct arguments_definition : if_must { }; @@ -625,7 +645,7 @@ struct field_definition_content { }; -// http://spec.graphql.org/June2018/#FieldDefinition +// https://spec.graphql.org/October2021/#FieldDefinition struct field_definition : if_must { }; @@ -635,7 +655,7 @@ struct fields_definition_content { }; -// http://spec.graphql.org/June2018/#FieldsDefinition +// https://spec.graphql.org/October2021/#FieldsDefinition struct fields_definition : if_must, fields_definition_content> { }; @@ -650,7 +670,7 @@ struct implements_interfaces_content { }; -// http://spec.graphql.org/June2018/#ImplementsInterfaces +// https://spec.graphql.org/October2021/#ImplementsInterfaces struct implements_interfaces : if_must { @@ -689,7 +709,7 @@ struct object_type_definition_content { }; -// http://spec.graphql.org/June2018/#ObjectTypeDefinition +// https://spec.graphql.org/October2021/#ObjectTypeDefinition struct object_type_definition : if_must { @@ -711,7 +731,11 @@ struct interface_type_definition_interface_name : seq, interface_n { }; -struct interface_type_definition_directives : opt, directives> +struct interface_type_definition_implements_interfaces : opt, implements_interfaces> +{ +}; + +struct interface_type_definition_directives : seq, directives> { }; @@ -721,13 +745,17 @@ struct interface_type_definition_fields_definition : seq, fields_d struct interface_type_definition_content : seq, + interface_type_definition_fields_definition>, + seq, - interface_type_definition_directives>> + interface_type_definition_implements_interfaces>> { }; -// http://spec.graphql.org/June2018/#InterfaceTypeDefinition +// https://spec.graphql.org/October2021/#InterfaceTypeDefinition struct interface_type_definition : if_must { @@ -755,7 +783,7 @@ struct union_member_types_content { }; -// http://spec.graphql.org/June2018/#UnionMemberTypes +// https://spec.graphql.org/October2021/#UnionMemberTypes struct union_member_types : if_must { }; @@ -775,7 +803,7 @@ struct union_type_definition_content { }; -// http://spec.graphql.org/June2018/#UnionTypeDefinition +// https://spec.graphql.org/October2021/#UnionTypeDefinition struct union_type_definition : if_must { }; @@ -796,7 +824,7 @@ struct enum_value_definition_content : opt, directives> { }; -// http://spec.graphql.org/June2018/#EnumValueDefinition +// https://spec.graphql.org/October2021/#EnumValueDefinition struct enum_value_definition : if_must { }; @@ -810,7 +838,7 @@ struct enum_values_definition_content { }; -// http://spec.graphql.org/June2018/#EnumValuesDefinition +// https://spec.graphql.org/October2021/#EnumValuesDefinition struct enum_values_definition : if_must { @@ -839,7 +867,7 @@ struct enum_type_definition_content { }; -// http://spec.graphql.org/June2018/#EnumTypeDefinition +// https://spec.graphql.org/October2021/#EnumTypeDefinition struct enum_type_definition : if_must { }; @@ -871,7 +899,7 @@ struct input_field_definition_content { }; -// http://spec.graphql.org/June2018/#InputValueDefinition +// https://spec.graphql.org/October2021/#InputValueDefinition struct input_field_definition : if_must { @@ -886,7 +914,7 @@ struct input_fields_definition_content { }; -// http://spec.graphql.org/June2018/#InputFieldsDefinition +// https://spec.graphql.org/October2021/#InputFieldsDefinition struct input_fields_definition : if_must { @@ -916,20 +944,20 @@ struct input_object_type_definition_content { }; -// http://spec.graphql.org/June2018/#InputObjectTypeDefinition +// https://spec.graphql.org/October2021/#InputObjectTypeDefinition struct input_object_type_definition : if_must { }; -// http://spec.graphql.org/June2018/#TypeDefinition +// https://spec.graphql.org/October2021/#TypeDefinition struct type_definition : sor { }; -// http://spec.graphql.org/June2018/#ExecutableDirectiveLocation +// https://spec.graphql.org/October2021/#ExecutableDirectiveLocation struct executable_directive_location : sor { }; -// http://spec.graphql.org/June2018/#DirectiveLocations +// https://spec.graphql.org/October2021/#DirectiveLocations struct directive_locations : seq, star>, list, one<'|'>, star>>> @@ -965,18 +993,23 @@ struct directive_definition_start { }; +struct repeatable_keyword : TAO_PEGTL_KEYWORD("repeatable") +{ +}; + struct directive_definition_content : seq, one<'@'>, directive_name, opt, arguments_definition>, - plus, on_keyword, plus, directive_locations> + opt, repeatable_keyword>, plus, on_keyword, plus, + directive_locations> { }; -// http://spec.graphql.org/June2018/#DirectiveDefinition +// https://spec.graphql.org/October2021/#DirectiveDefinition struct directive_definition : if_must { }; -// http://spec.graphql.org/June2018/#TypeSystemDefinition +// https://spec.graphql.org/October2021/#TypeSystemDefinition struct type_system_definition : sor { }; @@ -985,7 +1018,7 @@ struct extend_keyword : TAO_PEGTL_KEYWORD("extend") { }; -// http://spec.graphql.org/June2018/#OperationTypeDefinition +// https://spec.graphql.org/October2021/#OperationTypeDefinition struct operation_type_definition : root_operation_definition { }; @@ -1006,7 +1039,7 @@ struct schema_extension_content { }; -// http://spec.graphql.org/June2018/#SchemaExtension +// https://spec.graphql.org/October2021/#SchemaExtension struct schema_extension : if_must { }; @@ -1019,7 +1052,7 @@ struct scalar_type_extension_content : seq, scalar_name, star { }; @@ -1050,7 +1083,7 @@ struct object_type_extension_content { }; -// http://spec.graphql.org/June2018/#ObjectTypeExtension +// https://spec.graphql.org/October2021/#ObjectTypeExtension struct object_type_extension : if_must { }; @@ -1059,13 +1092,30 @@ struct interface_type_extension_start : seq, inter { }; +struct interface_type_extension_implements_interfaces : seq, implements_interfaces> +{ +}; + +struct interface_type_extension_directives : seq, directives> +{ +}; + +struct interface_type_extension_fields_definition : seq, fields_definition> +{ +}; + struct interface_type_extension_content : seq, interface_name, star, - sor>, fields_definition>, directives>> + sor, + opt, + interface_type_extension_fields_definition>, + seq, + interface_type_extension_directives>, + interface_type_extension_implements_interfaces>> { }; -// http://spec.graphql.org/June2018/#InterfaceTypeExtension +// https://spec.graphql.org/October2021/#InterfaceTypeExtension struct interface_type_extension : if_must { @@ -1081,7 +1131,7 @@ struct union_type_extension_content { }; -// http://spec.graphql.org/June2018/#UnionTypeExtension +// https://spec.graphql.org/October2021/#UnionTypeExtension struct union_type_extension : if_must { }; @@ -1096,7 +1146,7 @@ struct enum_type_extension_content { }; -// http://spec.graphql.org/June2018/#EnumTypeExtension +// https://spec.graphql.org/October2021/#EnumTypeExtension struct enum_type_extension : if_must { }; @@ -1111,25 +1161,25 @@ struct input_object_type_extension_content { }; -// http://spec.graphql.org/June2018/#InputObjectTypeExtension +// https://spec.graphql.org/October2021/#InputObjectTypeExtension struct input_object_type_extension : if_must { }; -// http://spec.graphql.org/June2018/#TypeExtension +// https://spec.graphql.org/October2021/#TypeExtension struct type_extension : sor { }; -// http://spec.graphql.org/June2018/#TypeSystemExtension +// https://spec.graphql.org/October2021/#TypeSystemExtension struct type_system_extension : sor { }; -// http://spec.graphql.org/June2018/#Definition +// https://spec.graphql.org/October2021/#Definition struct mixed_definition : sor { }; @@ -1141,7 +1191,7 @@ struct mixed_document_content { }; -// http://spec.graphql.org/June2018/#Document +// https://spec.graphql.org/October2021/#Document struct mixed_document : must { }; @@ -1153,12 +1203,12 @@ struct executable_document_content { }; -// http://spec.graphql.org/June2018/#Document +// https://spec.graphql.org/October2021/#Document struct executable_document : must { }; -// http://spec.graphql.org/June2018/#Definition +// https://spec.graphql.org/October2021/#Definition struct schema_type_definition : sor { }; @@ -1170,7 +1220,7 @@ struct schema_document_content { }; -// http://spec.graphql.org/June2018/#Document +// https://spec.graphql.org/October2021/#Document struct schema_document : must { }; diff --git a/include/graphqlservice/internal/Introspection.h b/include/graphqlservice/internal/Introspection.h index 0049b6ef..b2448e62 100644 --- a/include/graphqlservice/internal/Introspection.h +++ b/include/graphqlservice/internal/Introspection.h @@ -25,6 +25,7 @@ class Schema GRAPHQLINTROSPECTION_EXPORT explicit Schema(const std::shared_ptr& schema); // Accessors + GRAPHQLINTROSPECTION_EXPORT std::optional getDescription() const; GRAPHQLINTROSPECTION_EXPORT std::vector> getTypes() const; GRAPHQLINTROSPECTION_EXPORT std::shared_ptr getQueryType() const; GRAPHQLINTROSPECTION_EXPORT std::shared_ptr getMutationType() const; @@ -56,6 +57,7 @@ class Type GRAPHQLINTROSPECTION_EXPORT std::optional>> getInputFields() const; GRAPHQLINTROSPECTION_EXPORT std::shared_ptr getOfType() const; + GRAPHQLINTROSPECTION_EXPORT std::optional getSpecifiedByURL() const; private: const std::shared_ptr _type; @@ -121,6 +123,7 @@ class Directive GRAPHQLINTROSPECTION_EXPORT std::optional getDescription() const; GRAPHQLINTROSPECTION_EXPORT std::vector getLocations() const; GRAPHQLINTROSPECTION_EXPORT std::vector> getArgs() const; + GRAPHQLINTROSPECTION_EXPORT bool getIsRepeatable() const; private: const std::shared_ptr _directive; diff --git a/include/graphqlservice/internal/Schema.h b/include/graphqlservice/internal/Schema.h index 4e26b694..3d7d12b3 100644 --- a/include/graphqlservice/internal/Schema.h +++ b/include/graphqlservice/internal/Schema.h @@ -37,7 +37,8 @@ class EnumValue; class Schema : public std::enable_shared_from_this { public: - GRAPHQLSERVICE_EXPORT explicit Schema(bool noIntrospection = false); + GRAPHQLSERVICE_EXPORT explicit Schema( + bool noIntrospection = false, std::string_view description = ""); GRAPHQLSERVICE_EXPORT void AddQueryType(std::shared_ptr query); GRAPHQLSERVICE_EXPORT void AddMutationType(std::shared_ptr mutation); @@ -51,6 +52,7 @@ class Schema : public std::enable_shared_from_this // Accessors GRAPHQLSERVICE_EXPORT bool supportsIntrospection() const noexcept; + GRAPHQLSERVICE_EXPORT std::string_view description() const noexcept; GRAPHQLSERVICE_EXPORT const std::vector< std::pair>>& types() const noexcept; @@ -63,6 +65,7 @@ class Schema : public std::enable_shared_from_this private: const bool _noIntrospection = false; + const std::string_view _description; std::shared_ptr _query; std::shared_ptr _mutation; @@ -98,6 +101,7 @@ class BaseType : public std::enable_shared_from_this GRAPHQLSERVICE_EXPORT virtual const std::vector>& inputFields() const noexcept; GRAPHQLSERVICE_EXPORT virtual const std::weak_ptr& ofType() const noexcept; + GRAPHQLSERVICE_EXPORT virtual std::string_view specifiedByURL() const noexcept; protected: BaseType(introspection::TypeKind kind, std::string_view description); @@ -118,13 +122,15 @@ class ScalarType : public BaseType explicit ScalarType(init&& params); GRAPHQLSERVICE_EXPORT static std::shared_ptr Make( - std::string_view name, std::string_view description); + std::string_view name, std::string_view description, std::string_view specifiedByURL); // Accessors GRAPHQLSERVICE_EXPORT std::string_view name() const noexcept final; + GRAPHQLSERVICE_EXPORT std::string_view specifiedByURL() const noexcept final; private: const std::string_view _name; + const std::string_view _specifiedByURL; }; class ObjectType : public BaseType @@ -171,7 +177,9 @@ class InterfaceType : public BaseType GRAPHQLSERVICE_EXPORT static std::shared_ptr Make( std::string_view name, std::string_view description); - GRAPHQLSERVICE_EXPORT void AddPossibleType(std::weak_ptr possibleType); + GRAPHQLSERVICE_EXPORT void AddPossibleType(std::weak_ptr possibleType); + GRAPHQLSERVICE_EXPORT void AddInterfaces( + std::vector>&& interfaces); GRAPHQLSERVICE_EXPORT void AddFields(std::vector>&& fields); // Accessors @@ -180,10 +188,13 @@ class InterfaceType : public BaseType const noexcept final; GRAPHQLSERVICE_EXPORT const std::vector>& possibleTypes() const noexcept final; + GRAPHQLSERVICE_EXPORT const std::vector>& interfaces() + const noexcept final; private: const std::string_view _name; + std::vector> _interfaces; std::vector> _fields; std::vector> _possibleTypes; }; @@ -389,7 +400,7 @@ class Directive : public std::enable_shared_from_this GRAPHQLSERVICE_EXPORT static std::shared_ptr Make(std::string_view name, std::string_view description, std::vector&& locations, - std::vector>&& args = {}); + std::vector>&& args, bool isRepeatable); // Accessors GRAPHQLSERVICE_EXPORT std::string_view name() const noexcept; @@ -398,12 +409,14 @@ class Directive : public std::enable_shared_from_this const noexcept; GRAPHQLSERVICE_EXPORT const std::vector>& args() const noexcept; + GRAPHQLSERVICE_EXPORT bool isRepeatable() const noexcept; private: const std::string_view _name; const std::string_view _description; const std::vector _locations; const std::vector> _args; + const bool _isRepeatable; }; } // namespace schema diff --git a/include/graphqlservice/introspection/DirectiveObject.h b/include/graphqlservice/introspection/DirectiveObject.h index e0953288..2349fe17 100644 --- a/include/graphqlservice/introspection/DirectiveObject.h +++ b/include/graphqlservice/introspection/DirectiveObject.h @@ -20,6 +20,7 @@ class Directive service::AwaitableResolver resolveDescription(service::ResolverParams&& params) const; service::AwaitableResolver resolveLocations(service::ResolverParams&& params) const; service::AwaitableResolver resolveArgs(service::ResolverParams&& params) const; + service::AwaitableResolver resolveIsRepeatable(service::ResolverParams&& params) const; service::AwaitableResolver resolve_typename(service::ResolverParams&& params) const; @@ -31,6 +32,7 @@ class Directive virtual service::FieldResult> getDescription() const = 0; virtual service::FieldResult> getLocations() const = 0; virtual service::FieldResult>> getArgs() const = 0; + virtual service::FieldResult getIsRepeatable() const = 0; }; template @@ -62,6 +64,11 @@ class Directive return { _pimpl->getArgs() }; } + service::FieldResult getIsRepeatable() const final + { + return { _pimpl->getIsRepeatable() }; + } + private: const std::shared_ptr _pimpl; }; diff --git a/include/graphqlservice/introspection/IntrospectionSchema.h b/include/graphqlservice/introspection/IntrospectionSchema.h index a701780a..d833ce84 100644 --- a/include/graphqlservice/introspection/IntrospectionSchema.h +++ b/include/graphqlservice/introspection/IntrospectionSchema.h @@ -54,6 +54,7 @@ enum class DirectiveLocation FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, + VARIABLE_DEFINITION, SCHEMA, SCALAR, OBJECT, diff --git a/include/graphqlservice/introspection/SchemaObject.h b/include/graphqlservice/introspection/SchemaObject.h index f81ab9c3..9a4b082a 100644 --- a/include/graphqlservice/introspection/SchemaObject.h +++ b/include/graphqlservice/introspection/SchemaObject.h @@ -16,6 +16,7 @@ class Schema : public service::Object { private: + service::AwaitableResolver resolveDescription(service::ResolverParams&& params) const; service::AwaitableResolver resolveTypes(service::ResolverParams&& params) const; service::AwaitableResolver resolveQueryType(service::ResolverParams&& params) const; service::AwaitableResolver resolveMutationType(service::ResolverParams&& params) const; @@ -28,6 +29,7 @@ class Schema { virtual ~Concept() = default; + virtual service::FieldResult> getDescription() const = 0; virtual service::FieldResult>> getTypes() const = 0; virtual service::FieldResult> getQueryType() const = 0; virtual service::FieldResult> getMutationType() const = 0; @@ -44,6 +46,11 @@ class Schema { } + service::FieldResult> getDescription() const final + { + return { _pimpl->getDescription() }; + } + service::FieldResult>> getTypes() const final { return { _pimpl->getTypes() }; diff --git a/include/graphqlservice/introspection/TypeObject.h b/include/graphqlservice/introspection/TypeObject.h index 87c2101e..484980e1 100644 --- a/include/graphqlservice/introspection/TypeObject.h +++ b/include/graphqlservice/introspection/TypeObject.h @@ -25,6 +25,7 @@ class Type service::AwaitableResolver resolveEnumValues(service::ResolverParams&& params) const; service::AwaitableResolver resolveInputFields(service::ResolverParams&& params) const; service::AwaitableResolver resolveOfType(service::ResolverParams&& params) const; + service::AwaitableResolver resolveSpecifiedByURL(service::ResolverParams&& params) const; service::AwaitableResolver resolve_typename(service::ResolverParams&& params) const; @@ -41,6 +42,7 @@ class Type virtual service::FieldResult>>> getEnumValues(std::optional&& includeDeprecatedArg) const = 0; virtual service::FieldResult>>> getInputFields() const = 0; virtual service::FieldResult> getOfType() const = 0; + virtual service::FieldResult> getSpecifiedByURL() const = 0; }; template @@ -97,6 +99,11 @@ class Type return { _pimpl->getOfType() }; } + service::FieldResult> getSpecifiedByURL() const final + { + return { _pimpl->getSpecifiedByURL() }; + } + private: const std::shared_ptr _pimpl; }; diff --git a/samples/learn/DroidData.cpp b/samples/learn/DroidData.cpp index 79793c21..630d6ee8 100644 --- a/samples/learn/DroidData.cpp +++ b/samples/learn/DroidData.cpp @@ -55,11 +55,7 @@ std::optional>> Droid::getFriends [](const auto& wpFriend) noexcept { return make_hero(wpFriend); }); - result.erase(std::remove_if(result.begin(), - result.end(), - [](const auto& entry) noexcept { - return !entry; - }), + result.erase(std::remove(result.begin(), result.end(), std::shared_ptr {}), result.end()); return result.empty() ? std::nullopt : std::make_optional(std::move(result)); diff --git a/samples/learn/HumanData.cpp b/samples/learn/HumanData.cpp index 5364e115..4650cb94 100644 --- a/samples/learn/HumanData.cpp +++ b/samples/learn/HumanData.cpp @@ -54,11 +54,7 @@ std::optional>> Human::getFriends [](const auto& wpFriend) noexcept { return make_hero(wpFriend); }); - result.erase(std::remove_if(result.begin(), - result.end(), - [](const auto& entry) noexcept { - return !entry; - }), + result.erase(std::remove(result.begin(), result.end(), std::shared_ptr {}), result.end()); return result.empty() ? std::nullopt : std::make_optional(std::move(result)); diff --git a/samples/learn/schema/StarWarsSchema.cpp b/samples/learn/schema/StarWarsSchema.cpp index c5186dc7..929485d0 100644 --- a/samples/learn/schema/StarWarsSchema.cpp +++ b/samples/learn/schema/StarWarsSchema.cpp @@ -6,7 +6,6 @@ #include "QueryObject.h" #include "MutationObject.h" - #include "graphqlservice/internal/Schema.h" #include "graphqlservice/introspection/IntrospectionSchema.h" @@ -97,15 +96,15 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddType(R"gql(ReviewInput)gql"sv, typeReviewInput); auto typeCharacter = schema::InterfaceType::Make(R"gql(Character)gql"sv, R"md()md"sv); schema->AddType(R"gql(Character)gql"sv, typeCharacter); - auto typeHuman = schema::ObjectType::Make(R"gql(Human)gql"sv, R"md()md"); + auto typeHuman = schema::ObjectType::Make(R"gql(Human)gql"sv, R"md()md"sv); schema->AddType(R"gql(Human)gql"sv, typeHuman); - auto typeDroid = schema::ObjectType::Make(R"gql(Droid)gql"sv, R"md()md"); + auto typeDroid = schema::ObjectType::Make(R"gql(Droid)gql"sv, R"md()md"sv); schema->AddType(R"gql(Droid)gql"sv, typeDroid); - auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"); + auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"sv); schema->AddType(R"gql(Query)gql"sv, typeQuery); - auto typeReview = schema::ObjectType::Make(R"gql(Review)gql"sv, R"md()md"); + auto typeReview = schema::ObjectType::Make(R"gql(Review)gql"sv, R"md()md"sv); schema->AddType(R"gql(Review)gql"sv, typeReview); - auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"); + auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"sv); schema->AddType(R"gql(Mutation)gql"sv, typeMutation); typeEpisode->AddEnumValues({ @@ -138,7 +137,7 @@ std::shared_ptr GetSchema() if (!schema) { - schema = std::make_shared(false); + schema = std::make_shared(false, R"md()md"sv); introspection::AddTypesToSchema(schema); AddTypesToSchema(schema); s_wpSchema = schema; diff --git a/samples/today/TodayMock.cpp b/samples/today/TodayMock.cpp index 1e1bd736..62baed4a 100644 --- a/samples/today/TodayMock.cpp +++ b/samples/today/TodayMock.cpp @@ -4,12 +4,12 @@ #include "TodayMock.h" #include "AppointmentConnectionObject.h" -#include "TaskConnectionObject.h" +#include "CompleteTaskPayloadObject.h" +#include "ExpensiveObject.h" #include "FolderConnectionObject.h" -#include "UnionTypeObject.h" #include "NestedTypeObject.h" -#include "ExpensiveObject.h" -#include "CompleteTaskPayloadObject.h" +#include "TaskConnectionObject.h" +#include "UnionTypeObject.h" #include #include @@ -501,10 +501,16 @@ std::stack NestedType::_capturedParams; NestedType::NestedType(service::FieldParams&& params, int depth) : depth(depth) { - _capturedParams.push({ response::Value(params.operationDirectives), - response::Value(params.fragmentDefinitionDirectives), - response::Value(params.fragmentSpreadDirectives), - response::Value(params.inlineFragmentDirectives), + _capturedParams.push({ { params.operationDirectives }, + params.fragmentDefinitionDirectives->empty() + ? service::Directives {} + : service::Directives { params.fragmentDefinitionDirectives->front().get() }, + params.fragmentSpreadDirectives->empty() + ? service::Directives {} + : service::Directives { params.fragmentSpreadDirectives->front() }, + params.inlineFragmentDirectives->empty() + ? service::Directives {} + : service::Directives { params.inlineFragmentDirectives->front() }, std::move(params.fieldDirectives) }); } diff --git a/samples/today/TodayMock.h b/samples/today/TodayMock.h index 00bb84cb..79d26baa 100644 --- a/samples/today/TodayMock.h +++ b/samples/today/TodayMock.h @@ -571,13 +571,13 @@ class NodeChange struct CapturedParams { // Copied in the constructor - const response::Value operationDirectives; - const response::Value fragmentDefinitionDirectives; - const response::Value fragmentSpreadDirectives; - const response::Value inlineFragmentDirectives; + const service::Directives operationDirectives; + const service::Directives fragmentDefinitionDirectives; + const service::Directives fragmentSpreadDirectives; + const service::Directives inlineFragmentDirectives; // Moved in the constructor - const response::Value fieldDirectives; + const service::Directives fieldDirectives; }; class NestedType diff --git a/samples/today/nointrospection/SubscriptionObject.cpp b/samples/today/nointrospection/SubscriptionObject.cpp index ac6013b3..00d348ce 100644 --- a/samples/today/nointrospection/SubscriptionObject.cpp +++ b/samples/today/nointrospection/SubscriptionObject.cpp @@ -85,7 +85,7 @@ service::AwaitableResolver Subscription::resolve_typename(service::ResolverParam void AddSubscriptionDetails(const std::shared_ptr& typeSubscription, const std::shared_ptr& schema) { typeSubscription->AddFields({ - schema::Field::Make(R"gql(nextAppointmentChange)gql"sv, R"md()md"sv, std::make_optional(R"md(Need to deprecate a [field](http://spec.graphql.org/June2018/#sec-Deprecation))md"sv), schema->LookupType(R"gql(Appointment)gql"sv)), + schema::Field::Make(R"gql(nextAppointmentChange)gql"sv, R"md()md"sv, std::make_optional(R"md(Need to deprecate a [field](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation))md"sv), schema->LookupType(R"gql(Appointment)gql"sv)), schema::Field::Make(R"gql(nodeChange)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Node)gql"sv)), { schema::InputValue::Make(R"gql(id)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(ID)gql"sv)), R"gql()gql"sv) }) diff --git a/samples/today/nointrospection/TodaySchema.cpp b/samples/today/nointrospection/TodaySchema.cpp index c767a52b..b251a3dc 100644 --- a/samples/today/nointrospection/TodaySchema.cpp +++ b/samples/today/nointrospection/TodaySchema.cpp @@ -7,7 +7,6 @@ #include "MutationObject.h" #include "SubscriptionObject.h" - #include "graphqlservice/internal/Schema.h" #include "graphqlservice/introspection/IntrospectionSchema.h" @@ -159,8 +158,8 @@ Operations::Operations(std::shared_ptr query, std::shared_ptr& schema) { - schema->AddType(R"gql(ItemCursor)gql"sv, schema::ScalarType::Make(R"gql(ItemCursor)gql"sv, R"md()md")); - schema->AddType(R"gql(DateTime)gql"sv, schema::ScalarType::Make(R"gql(DateTime)gql"sv, R"md()md")); + schema->AddType(R"gql(ItemCursor)gql"sv, schema::ScalarType::Make(R"gql(ItemCursor)gql"sv, R"md()md", R"url()url"sv)); + schema->AddType(R"gql(DateTime)gql"sv, schema::ScalarType::Make(R"gql(DateTime)gql"sv, R"md()md", R"url()url"sv)); auto typeTaskState = schema::EnumType::Make(R"gql(TaskState)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskState)gql"sv, typeTaskState); auto typeCompleteTaskInput = schema::InputObjectType::Make(R"gql(CompleteTaskInput)gql"sv, R"md()md"sv); @@ -177,44 +176,44 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddType(R"gql(Node)gql"sv, typeNode); auto typeUnionType = schema::UnionType::Make(R"gql(UnionType)gql"sv, R"md()md"sv); schema->AddType(R"gql(UnionType)gql"sv, typeUnionType); - auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"); + auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"sv); schema->AddType(R"gql(Query)gql"sv, typeQuery); - auto typePageInfo = schema::ObjectType::Make(R"gql(PageInfo)gql"sv, R"md()md"); + auto typePageInfo = schema::ObjectType::Make(R"gql(PageInfo)gql"sv, R"md()md"sv); schema->AddType(R"gql(PageInfo)gql"sv, typePageInfo); - auto typeAppointmentEdge = schema::ObjectType::Make(R"gql(AppointmentEdge)gql"sv, R"md()md"); + auto typeAppointmentEdge = schema::ObjectType::Make(R"gql(AppointmentEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(AppointmentEdge)gql"sv, typeAppointmentEdge); - auto typeAppointmentConnection = schema::ObjectType::Make(R"gql(AppointmentConnection)gql"sv, R"md()md"); + auto typeAppointmentConnection = schema::ObjectType::Make(R"gql(AppointmentConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(AppointmentConnection)gql"sv, typeAppointmentConnection); - auto typeTaskEdge = schema::ObjectType::Make(R"gql(TaskEdge)gql"sv, R"md()md"); + auto typeTaskEdge = schema::ObjectType::Make(R"gql(TaskEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskEdge)gql"sv, typeTaskEdge); - auto typeTaskConnection = schema::ObjectType::Make(R"gql(TaskConnection)gql"sv, R"md()md"); + auto typeTaskConnection = schema::ObjectType::Make(R"gql(TaskConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskConnection)gql"sv, typeTaskConnection); - auto typeFolderEdge = schema::ObjectType::Make(R"gql(FolderEdge)gql"sv, R"md()md"); + auto typeFolderEdge = schema::ObjectType::Make(R"gql(FolderEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(FolderEdge)gql"sv, typeFolderEdge); - auto typeFolderConnection = schema::ObjectType::Make(R"gql(FolderConnection)gql"sv, R"md()md"); + auto typeFolderConnection = schema::ObjectType::Make(R"gql(FolderConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(FolderConnection)gql"sv, typeFolderConnection); - auto typeCompleteTaskPayload = schema::ObjectType::Make(R"gql(CompleteTaskPayload)gql"sv, R"md()md"); + auto typeCompleteTaskPayload = schema::ObjectType::Make(R"gql(CompleteTaskPayload)gql"sv, R"md()md"sv); schema->AddType(R"gql(CompleteTaskPayload)gql"sv, typeCompleteTaskPayload); - auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"); + auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"sv); schema->AddType(R"gql(Mutation)gql"sv, typeMutation); - auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md()md"); + auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md()md"sv); schema->AddType(R"gql(Subscription)gql"sv, typeSubscription); - auto typeAppointment = schema::ObjectType::Make(R"gql(Appointment)gql"sv, R"md()md"); + auto typeAppointment = schema::ObjectType::Make(R"gql(Appointment)gql"sv, R"md()md"sv); schema->AddType(R"gql(Appointment)gql"sv, typeAppointment); - auto typeTask = schema::ObjectType::Make(R"gql(Task)gql"sv, R"md()md"); + auto typeTask = schema::ObjectType::Make(R"gql(Task)gql"sv, R"md()md"sv); schema->AddType(R"gql(Task)gql"sv, typeTask); - auto typeFolder = schema::ObjectType::Make(R"gql(Folder)gql"sv, R"md()md"); + auto typeFolder = schema::ObjectType::Make(R"gql(Folder)gql"sv, R"md()md"sv); schema->AddType(R"gql(Folder)gql"sv, typeFolder); - auto typeNestedType = schema::ObjectType::Make(R"gql(NestedType)gql"sv, R"md()md"); + auto typeNestedType = schema::ObjectType::Make(R"gql(NestedType)gql"sv, R"md()md"sv); schema->AddType(R"gql(NestedType)gql"sv, typeNestedType); - auto typeExpensive = schema::ObjectType::Make(R"gql(Expensive)gql"sv, R"md()md"); + auto typeExpensive = schema::ObjectType::Make(R"gql(Expensive)gql"sv, R"md()md"sv); schema->AddType(R"gql(Expensive)gql"sv, typeExpensive); typeTaskState->AddEnumValues({ { service::s_namesTaskState[static_cast(today::TaskState::New)], R"md()md"sv, std::nullopt }, { service::s_namesTaskState[static_cast(today::TaskState::Started)], R"md()md"sv, std::nullopt }, { service::s_namesTaskState[static_cast(today::TaskState::Complete)], R"md()md"sv, std::nullopt }, - { service::s_namesTaskState[static_cast(today::TaskState::Unassigned)], R"md()md"sv, std::make_optional(R"md(Need to deprecate an [enum value](http://spec.graphql.org/June2018/#sec-Deprecation))md"sv) } + { service::s_namesTaskState[static_cast(today::TaskState::Unassigned)], R"md()md"sv, std::make_optional(R"md(Need to deprecate an [enum value](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation))md"sv) } }); typeCompleteTaskInput->AddInputValues({ @@ -262,37 +261,35 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddDirective(schema::Directive::Make(R"gql(id)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD_DEFINITION - })); - schema->AddDirective(schema::Directive::Make(R"gql(subscriptionTag)gql"sv, R"md()md"sv, { - introspection::DirectiveLocation::SUBSCRIPTION - }, { - schema::InputValue::Make(R"gql(field)gql"sv, R"md()md"sv, schema->LookupType(R"gql(String)gql"sv), R"gql()gql"sv) - })); + }, {}, false)); schema->AddDirective(schema::Directive::Make(R"gql(queryTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::QUERY }, { schema::InputValue::Make(R"gql(query)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fieldTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD }, { schema::InputValue::Make(R"gql(field)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fragmentDefinitionTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FRAGMENT_DEFINITION }, { schema::InputValue::Make(R"gql(fragmentDefinition)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fragmentSpreadTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FRAGMENT_SPREAD }, { schema::InputValue::Make(R"gql(fragmentSpread)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(inlineFragmentTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::INLINE_FRAGMENT }, { schema::InputValue::Make(R"gql(inlineFragment)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); + schema->AddDirective(schema::Directive::Make(R"gql(repeatableOnField)gql"sv, R"md()md"sv, { + introspection::DirectiveLocation::FIELD + }, {}, true)); schema->AddQueryType(typeQuery); schema->AddMutationType(typeMutation); @@ -306,7 +303,7 @@ std::shared_ptr GetSchema() if (!schema) { - schema = std::make_shared(true); + schema = std::make_shared(true, R"md()md"sv); introspection::AddTypesToSchema(schema); AddTypesToSchema(schema); s_wpSchema = schema; diff --git a/samples/today/schema.today.graphql b/samples/today/schema.today.graphql index 0e2bb3e8..53056ab0 100644 --- a/samples/today/schema.today.graphql +++ b/samples/today/schema.today.graphql @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +"Test Schema based on a dashboard showing daily appointments, tasks, and email folders with unread counts." schema { query: Query mutation: Mutation @@ -95,23 +96,29 @@ type Mutation { setFloat(value: Float!): Float! } + """ + + Subscription type: + + 2nd line... + 3rd line goes here! + + """ type Subscription { nextAppointmentChange : Appointment @deprecated( - reason:"""Need to deprecate a [field](http://spec.graphql.org/June2018/#sec-Deprecation)""" + reason:"""Need to deprecate a [field](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation)""" ) nodeChange(id: ID!): Node! } -directive @subscriptionTag(field: String) on SUBSCRIPTION - -scalar DateTime +scalar DateTime @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601") enum TaskState { New Started Complete Unassigned @deprecated( - reason:"""Need to deprecate an [enum value](http://spec.graphql.org/June2018/#sec-Deprecation)""" + reason:"""Need to deprecate an [enum value](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation)""" ) } @@ -142,6 +149,7 @@ directive @fieldTag(field: String!) on FIELD directive @fragmentDefinitionTag(fragmentDefinition: String!) on FRAGMENT_DEFINITION directive @fragmentSpreadTag(fragmentSpread: String!) on FRAGMENT_SPREAD directive @inlineFragmentTag(inlineFragment: String!) on INLINE_FRAGMENT +directive @repeatableOnField repeatable on FIELD "Infinitely nestable type which can be used with nested fragments to test directive handling" type NestedType { diff --git a/samples/today/schema/SubscriptionObject.cpp b/samples/today/schema/SubscriptionObject.cpp index ac6013b3..00d348ce 100644 --- a/samples/today/schema/SubscriptionObject.cpp +++ b/samples/today/schema/SubscriptionObject.cpp @@ -85,7 +85,7 @@ service::AwaitableResolver Subscription::resolve_typename(service::ResolverParam void AddSubscriptionDetails(const std::shared_ptr& typeSubscription, const std::shared_ptr& schema) { typeSubscription->AddFields({ - schema::Field::Make(R"gql(nextAppointmentChange)gql"sv, R"md()md"sv, std::make_optional(R"md(Need to deprecate a [field](http://spec.graphql.org/June2018/#sec-Deprecation))md"sv), schema->LookupType(R"gql(Appointment)gql"sv)), + schema::Field::Make(R"gql(nextAppointmentChange)gql"sv, R"md()md"sv, std::make_optional(R"md(Need to deprecate a [field](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation))md"sv), schema->LookupType(R"gql(Appointment)gql"sv)), schema::Field::Make(R"gql(nodeChange)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Node)gql"sv)), { schema::InputValue::Make(R"gql(id)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(ID)gql"sv)), R"gql()gql"sv) }) diff --git a/samples/today/schema/TodaySchema.cpp b/samples/today/schema/TodaySchema.cpp index 5b5cdd4c..cabeee6a 100644 --- a/samples/today/schema/TodaySchema.cpp +++ b/samples/today/schema/TodaySchema.cpp @@ -7,7 +7,6 @@ #include "MutationObject.h" #include "SubscriptionObject.h" - #include "graphqlservice/internal/Schema.h" #include "graphqlservice/introspection/IntrospectionSchema.h" @@ -159,8 +158,8 @@ Operations::Operations(std::shared_ptr query, std::shared_ptr& schema) { - schema->AddType(R"gql(ItemCursor)gql"sv, schema::ScalarType::Make(R"gql(ItemCursor)gql"sv, R"md()md")); - schema->AddType(R"gql(DateTime)gql"sv, schema::ScalarType::Make(R"gql(DateTime)gql"sv, R"md()md")); + schema->AddType(R"gql(ItemCursor)gql"sv, schema::ScalarType::Make(R"gql(ItemCursor)gql"sv, R"md()md", R"url()url"sv)); + schema->AddType(R"gql(DateTime)gql"sv, schema::ScalarType::Make(R"gql(DateTime)gql"sv, R"md()md", R"url(https://en.wikipedia.org/wiki/ISO_8601)url"sv)); auto typeTaskState = schema::EnumType::Make(R"gql(TaskState)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskState)gql"sv, typeTaskState); auto typeCompleteTaskInput = schema::InputObjectType::Make(R"gql(CompleteTaskInput)gql"sv, R"md()md"sv); @@ -177,44 +176,47 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddType(R"gql(Node)gql"sv, typeNode); auto typeUnionType = schema::UnionType::Make(R"gql(UnionType)gql"sv, R"md()md"sv); schema->AddType(R"gql(UnionType)gql"sv, typeUnionType); - auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md(Root Query type)md"); + auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md(Root Query type)md"sv); schema->AddType(R"gql(Query)gql"sv, typeQuery); - auto typePageInfo = schema::ObjectType::Make(R"gql(PageInfo)gql"sv, R"md()md"); + auto typePageInfo = schema::ObjectType::Make(R"gql(PageInfo)gql"sv, R"md()md"sv); schema->AddType(R"gql(PageInfo)gql"sv, typePageInfo); - auto typeAppointmentEdge = schema::ObjectType::Make(R"gql(AppointmentEdge)gql"sv, R"md()md"); + auto typeAppointmentEdge = schema::ObjectType::Make(R"gql(AppointmentEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(AppointmentEdge)gql"sv, typeAppointmentEdge); - auto typeAppointmentConnection = schema::ObjectType::Make(R"gql(AppointmentConnection)gql"sv, R"md()md"); + auto typeAppointmentConnection = schema::ObjectType::Make(R"gql(AppointmentConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(AppointmentConnection)gql"sv, typeAppointmentConnection); - auto typeTaskEdge = schema::ObjectType::Make(R"gql(TaskEdge)gql"sv, R"md()md"); + auto typeTaskEdge = schema::ObjectType::Make(R"gql(TaskEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskEdge)gql"sv, typeTaskEdge); - auto typeTaskConnection = schema::ObjectType::Make(R"gql(TaskConnection)gql"sv, R"md()md"); + auto typeTaskConnection = schema::ObjectType::Make(R"gql(TaskConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(TaskConnection)gql"sv, typeTaskConnection); - auto typeFolderEdge = schema::ObjectType::Make(R"gql(FolderEdge)gql"sv, R"md()md"); + auto typeFolderEdge = schema::ObjectType::Make(R"gql(FolderEdge)gql"sv, R"md()md"sv); schema->AddType(R"gql(FolderEdge)gql"sv, typeFolderEdge); - auto typeFolderConnection = schema::ObjectType::Make(R"gql(FolderConnection)gql"sv, R"md()md"); + auto typeFolderConnection = schema::ObjectType::Make(R"gql(FolderConnection)gql"sv, R"md()md"sv); schema->AddType(R"gql(FolderConnection)gql"sv, typeFolderConnection); - auto typeCompleteTaskPayload = schema::ObjectType::Make(R"gql(CompleteTaskPayload)gql"sv, R"md()md"); + auto typeCompleteTaskPayload = schema::ObjectType::Make(R"gql(CompleteTaskPayload)gql"sv, R"md()md"sv); schema->AddType(R"gql(CompleteTaskPayload)gql"sv, typeCompleteTaskPayload); - auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"); + auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"sv); schema->AddType(R"gql(Mutation)gql"sv, typeMutation); - auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md()md"); + auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md(Subscription type: + +2nd line... + 3rd line goes here!)md"sv); schema->AddType(R"gql(Subscription)gql"sv, typeSubscription); - auto typeAppointment = schema::ObjectType::Make(R"gql(Appointment)gql"sv, R"md()md"); + auto typeAppointment = schema::ObjectType::Make(R"gql(Appointment)gql"sv, R"md()md"sv); schema->AddType(R"gql(Appointment)gql"sv, typeAppointment); - auto typeTask = schema::ObjectType::Make(R"gql(Task)gql"sv, R"md()md"); + auto typeTask = schema::ObjectType::Make(R"gql(Task)gql"sv, R"md()md"sv); schema->AddType(R"gql(Task)gql"sv, typeTask); - auto typeFolder = schema::ObjectType::Make(R"gql(Folder)gql"sv, R"md()md"); + auto typeFolder = schema::ObjectType::Make(R"gql(Folder)gql"sv, R"md()md"sv); schema->AddType(R"gql(Folder)gql"sv, typeFolder); - auto typeNestedType = schema::ObjectType::Make(R"gql(NestedType)gql"sv, R"md(Infinitely nestable type which can be used with nested fragments to test directive handling)md"); + auto typeNestedType = schema::ObjectType::Make(R"gql(NestedType)gql"sv, R"md(Infinitely nestable type which can be used with nested fragments to test directive handling)md"sv); schema->AddType(R"gql(NestedType)gql"sv, typeNestedType); - auto typeExpensive = schema::ObjectType::Make(R"gql(Expensive)gql"sv, R"md()md"); + auto typeExpensive = schema::ObjectType::Make(R"gql(Expensive)gql"sv, R"md()md"sv); schema->AddType(R"gql(Expensive)gql"sv, typeExpensive); typeTaskState->AddEnumValues({ { service::s_namesTaskState[static_cast(today::TaskState::New)], R"md()md"sv, std::nullopt }, { service::s_namesTaskState[static_cast(today::TaskState::Started)], R"md()md"sv, std::nullopt }, { service::s_namesTaskState[static_cast(today::TaskState::Complete)], R"md()md"sv, std::nullopt }, - { service::s_namesTaskState[static_cast(today::TaskState::Unassigned)], R"md()md"sv, std::make_optional(R"md(Need to deprecate an [enum value](http://spec.graphql.org/June2018/#sec-Deprecation))md"sv) } + { service::s_namesTaskState[static_cast(today::TaskState::Unassigned)], R"md()md"sv, std::make_optional(R"md(Need to deprecate an [enum value](https://spec.graphql.org/October2021/#sec-Schema-Introspection.Deprecation))md"sv) } }); typeCompleteTaskInput->AddInputValues({ @@ -262,37 +264,35 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddDirective(schema::Directive::Make(R"gql(id)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD_DEFINITION - })); - schema->AddDirective(schema::Directive::Make(R"gql(subscriptionTag)gql"sv, R"md()md"sv, { - introspection::DirectiveLocation::SUBSCRIPTION - }, { - schema::InputValue::Make(R"gql(field)gql"sv, R"md()md"sv, schema->LookupType(R"gql(String)gql"sv), R"gql()gql"sv) - })); + }, {}, false)); schema->AddDirective(schema::Directive::Make(R"gql(queryTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::QUERY }, { schema::InputValue::Make(R"gql(query)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fieldTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD }, { schema::InputValue::Make(R"gql(field)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fragmentDefinitionTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FRAGMENT_DEFINITION }, { schema::InputValue::Make(R"gql(fragmentDefinition)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(fragmentSpreadTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FRAGMENT_SPREAD }, { schema::InputValue::Make(R"gql(fragmentSpread)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(inlineFragmentTag)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::INLINE_FRAGMENT }, { schema::InputValue::Make(R"gql(inlineFragment)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) - })); + }, false)); + schema->AddDirective(schema::Directive::Make(R"gql(repeatableOnField)gql"sv, R"md()md"sv, { + introspection::DirectiveLocation::FIELD + }, {}, true)); schema->AddQueryType(typeQuery); schema->AddMutationType(typeMutation); @@ -306,7 +306,7 @@ std::shared_ptr GetSchema() if (!schema) { - schema = std::make_shared(false); + schema = std::make_shared(false, R"md(Test Schema based on a dashboard showing daily appointments, tasks, and email folders with unread counts.)md"sv); introspection::AddTypesToSchema(schema); AddTypesToSchema(schema); s_wpSchema = schema; diff --git a/samples/validation/schema/NodeObject.cpp b/samples/validation/schema/NodeObject.cpp new file mode 100644 index 00000000..3db93efb --- /dev/null +++ b/samples/validation/schema/NodeObject.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#include "NodeObject.h" + +#include "graphqlservice/internal/Schema.h" + +#include "graphqlservice/introspection/IntrospectionSchema.h" + +using namespace std::literals; + +namespace graphql::validation { +namespace object { + +Node::Node(std::unique_ptr&& pimpl) noexcept + : service::Object { pimpl->getTypeNames(), pimpl->getResolvers() } + , _pimpl { std::move(pimpl) } +{ +} + +void Node::beginSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->beginSelectionSet(params); +} + +void Node::endSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->endSelectionSet(params); +} + +} // namespace object + +void AddNodeDetails(const std::shared_ptr& typeNode, const std::shared_ptr& schema) +{ + typeNode->AddFields({ + schema::Field::Make(R"gql(id)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(ID)gql"sv))) + }); +} + +} // namespace graphql::validation diff --git a/samples/validation/schema/NodeObject.h b/samples/validation/schema/NodeObject.h new file mode 100644 index 00000000..3075e854 --- /dev/null +++ b/samples/validation/schema/NodeObject.h @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#pragma once + +#ifndef NODEOBJECT_H +#define NODEOBJECT_H + +#include "ValidationSchema.h" + +namespace graphql::validation::object { + +class Node + : public service::Object +{ +private: + struct Concept + { + virtual ~Concept() = default; + + virtual service::TypeNames getTypeNames() const noexcept = 0; + virtual service::ResolverMap getResolvers() const noexcept = 0; + + virtual void beginSelectionSet(const service::SelectionSetParams& params) const = 0; + virtual void endSelectionSet(const service::SelectionSetParams& params) const = 0; + }; + + template + struct Model + : Concept + { + Model(std::shared_ptr&& pimpl) noexcept + : _pimpl { std::move(pimpl) } + { + } + + service::TypeNames getTypeNames() const noexcept final + { + return _pimpl->getTypeNames(); + } + + service::ResolverMap getResolvers() const noexcept final + { + return _pimpl->getResolvers(); + } + + void beginSelectionSet(const service::SelectionSetParams& params) const final + { + _pimpl->beginSelectionSet(params); + } + + void endSelectionSet(const service::SelectionSetParams& params) const final + { + _pimpl->endSelectionSet(params); + } + + private: + const std::shared_ptr _pimpl; + }; + + Node(std::unique_ptr&& pimpl) noexcept; + + void beginSelectionSet(const service::SelectionSetParams& params) const final; + void endSelectionSet(const service::SelectionSetParams& params) const final; + + const std::unique_ptr _pimpl; + +public: + template + Node(std::shared_ptr pimpl) noexcept + : Node { std::unique_ptr { std::make_unique>(std::move(pimpl)) } } + { + static_assert(T::template implements(), "Node is not implemented"); + } +}; + +} // namespace graphql::validation::object + +#endif // NODEOBJECT_H diff --git a/samples/validation/schema/QueryObject.cpp b/samples/validation/schema/QueryObject.cpp index 31098673..80f0caa2 100644 --- a/samples/validation/schema/QueryObject.cpp +++ b/samples/validation/schema/QueryObject.cpp @@ -9,6 +9,7 @@ #include "PetObject.h" #include "CatOrDogObject.h" #include "ArgumentsObject.h" +#include "ResourceObject.h" #include "graphqlservice/internal/Schema.h" @@ -46,6 +47,7 @@ service::ResolverMap Query::getResolvers() const noexcept { R"gql(human)gql"sv, [this](service::ResolverParams&& params) { return resolveHuman(std::move(params)); } }, { R"gql(findDog)gql"sv, [this](service::ResolverParams&& params) { return resolveFindDog(std::move(params)); } }, { R"gql(catOrDog)gql"sv, [this](service::ResolverParams&& params) { return resolveCatOrDog(std::move(params)); } }, + { R"gql(resource)gql"sv, [this](service::ResolverParams&& params) { return resolveResource(std::move(params)); } }, { R"gql(arguments)gql"sv, [this](service::ResolverParams&& params) { return resolveArguments(std::move(params)); } }, { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, { R"gql(booleanList)gql"sv, [this](service::ResolverParams&& params) { return resolveBooleanList(std::move(params)); } } @@ -112,6 +114,16 @@ service::AwaitableResolver Query::resolveArguments(service::ResolverParams&& par return service::ModifiedResult::convert(std::move(result), std::move(params)); } +service::AwaitableResolver Query::resolveResource(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + auto directives = std::move(params.fieldDirectives); + auto result = _pimpl->getResource(service::FieldParams(service::SelectionSetParams{ params }, std::move(directives))); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + service::AwaitableResolver Query::resolveFindDog(service::ResolverParams&& params) const { auto argComplex = service::ModifiedArgument::require("complex", params.arguments); @@ -149,6 +161,7 @@ void AddQueryDetails(const std::shared_ptr& typeQuery, const schema::Field::Make(R"gql(pet)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(Pet)gql"sv)), schema::Field::Make(R"gql(catOrDog)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(CatOrDog)gql"sv)), schema::Field::Make(R"gql(arguments)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(Arguments)gql"sv)), + schema::Field::Make(R"gql(resource)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(Resource)gql"sv)), schema::Field::Make(R"gql(findDog)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(Dog)gql"sv), { schema::InputValue::Make(R"gql(complex)gql"sv, R"md()md"sv, schema->LookupType(R"gql(ComplexInput)gql"sv), R"gql()gql"sv) }), diff --git a/samples/validation/schema/QueryObject.h b/samples/validation/schema/QueryObject.h index d405aff7..c5d971a3 100644 --- a/samples/validation/schema/QueryObject.h +++ b/samples/validation/schema/QueryObject.h @@ -73,6 +73,18 @@ concept getArguments = requires (TImpl impl) { service::FieldResult> { impl.getArguments() } }; }; +template +concept getResourceWithParams = requires (TImpl impl, service::FieldParams params) +{ + { service::FieldResult> { impl.getResource(std::move(params)) } }; +}; + +template +concept getResource = requires (TImpl impl) +{ + { service::FieldResult> { impl.getResource() } }; +}; + template concept getFindDogWithParams = requires (TImpl impl, service::FieldParams params, std::optional complexArg) { @@ -120,6 +132,7 @@ class Query service::AwaitableResolver resolvePet(service::ResolverParams&& params) const; service::AwaitableResolver resolveCatOrDog(service::ResolverParams&& params) const; service::AwaitableResolver resolveArguments(service::ResolverParams&& params) const; + service::AwaitableResolver resolveResource(service::ResolverParams&& params) const; service::AwaitableResolver resolveFindDog(service::ResolverParams&& params) const; service::AwaitableResolver resolveBooleanList(service::ResolverParams&& params) const; @@ -137,6 +150,7 @@ class Query virtual service::FieldResult> getPet(service::FieldParams&& params) const = 0; virtual service::FieldResult> getCatOrDog(service::FieldParams&& params) const = 0; virtual service::FieldResult> getArguments(service::FieldParams&& params) const = 0; + virtual service::FieldResult> getResource(service::FieldParams&& params) const = 0; virtual service::FieldResult> getFindDog(service::FieldParams&& params, std::optional&& complexArg) const = 0; virtual service::FieldResult> getBooleanList(service::FieldParams&& params, std::optional>&& booleanListArgArg) const = 0; }; @@ -230,6 +244,22 @@ class Query } } + service::FieldResult> getResource(service::FieldParams&& params) const final + { + if constexpr (methods::QueryHas::getResourceWithParams) + { + return { _pimpl->getResource(std::move(params)) }; + } + else if constexpr (methods::QueryHas::getResource) + { + return { _pimpl->getResource() }; + } + else + { + throw std::runtime_error(R"ex(Query::getResource is not implemented)ex"); + } + } + service::FieldResult> getFindDog(service::FieldParams&& params, std::optional&& complexArg) const final { if constexpr (methods::QueryHas::getFindDogWithParams) diff --git a/samples/validation/schema/ResourceObject.cpp b/samples/validation/schema/ResourceObject.cpp new file mode 100644 index 00000000..4bea15ce --- /dev/null +++ b/samples/validation/schema/ResourceObject.cpp @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#include "ResourceObject.h" + +#include "graphqlservice/internal/Schema.h" + +#include "graphqlservice/introspection/IntrospectionSchema.h" + +using namespace std::literals; + +namespace graphql::validation { +namespace object { + +Resource::Resource(std::unique_ptr&& pimpl) noexcept + : service::Object { pimpl->getTypeNames(), pimpl->getResolvers() } + , _pimpl { std::move(pimpl) } +{ +} + +void Resource::beginSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->beginSelectionSet(params); +} + +void Resource::endSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->endSelectionSet(params); +} + +} // namespace object + +void AddResourceDetails(const std::shared_ptr& typeResource, const std::shared_ptr& schema) +{ + typeResource->AddInterfaces({ + std::static_pointer_cast(schema->LookupType(R"gql(Node)gql"sv)) + }); + typeResource->AddFields({ + schema::Field::Make(R"gql(id)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(ID)gql"sv))), + schema::Field::Make(R"gql(url)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv)) + }); +} + +} // namespace graphql::validation diff --git a/samples/validation/schema/ResourceObject.h b/samples/validation/schema/ResourceObject.h new file mode 100644 index 00000000..1fa71284 --- /dev/null +++ b/samples/validation/schema/ResourceObject.h @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#pragma once + +#ifndef RESOURCEOBJECT_H +#define RESOURCEOBJECT_H + +#include "ValidationSchema.h" + +namespace graphql::validation::object { + +class Resource + : public service::Object +{ +private: + struct Concept + { + virtual ~Concept() = default; + + virtual service::TypeNames getTypeNames() const noexcept = 0; + virtual service::ResolverMap getResolvers() const noexcept = 0; + + virtual void beginSelectionSet(const service::SelectionSetParams& params) const = 0; + virtual void endSelectionSet(const service::SelectionSetParams& params) const = 0; + }; + + template + struct Model + : Concept + { + Model(std::shared_ptr&& pimpl) noexcept + : _pimpl { std::move(pimpl) } + { + } + + service::TypeNames getTypeNames() const noexcept final + { + return _pimpl->getTypeNames(); + } + + service::ResolverMap getResolvers() const noexcept final + { + return _pimpl->getResolvers(); + } + + void beginSelectionSet(const service::SelectionSetParams& params) const final + { + _pimpl->beginSelectionSet(params); + } + + void endSelectionSet(const service::SelectionSetParams& params) const final + { + _pimpl->endSelectionSet(params); + } + + private: + const std::shared_ptr _pimpl; + }; + + Resource(std::unique_ptr&& pimpl) noexcept; + + void beginSelectionSet(const service::SelectionSetParams& params) const final; + void endSelectionSet(const service::SelectionSetParams& params) const final; + + const std::unique_ptr _pimpl; + +public: + template + Resource(std::shared_ptr pimpl) noexcept + : Resource { std::unique_ptr { std::make_unique>(std::move(pimpl)) } } + { + static_assert(T::template implements(), "Resource is not implemented"); + } +}; + +} // namespace graphql::validation::object + +#endif // RESOURCEOBJECT_H diff --git a/samples/validation/schema/ValidationSchema.cpp b/samples/validation/schema/ValidationSchema.cpp index 0a098016..c06e4f27 100644 --- a/samples/validation/schema/ValidationSchema.cpp +++ b/samples/validation/schema/ValidationSchema.cpp @@ -7,7 +7,6 @@ #include "MutationObject.h" #include "SubscriptionObject.h" - #include "graphqlservice/internal/Schema.h" #include "graphqlservice/introspection/IntrospectionSchema.h" @@ -140,31 +139,35 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddType(R"gql(Sentient)gql"sv, typeSentient); auto typePet = schema::InterfaceType::Make(R"gql(Pet)gql"sv, R"md()md"sv); schema->AddType(R"gql(Pet)gql"sv, typePet); + auto typeNode = schema::InterfaceType::Make(R"gql(Node)gql"sv, R"md()md"sv); + schema->AddType(R"gql(Node)gql"sv, typeNode); + auto typeResource = schema::InterfaceType::Make(R"gql(Resource)gql"sv, R"md()md"sv); + schema->AddType(R"gql(Resource)gql"sv, typeResource); auto typeCatOrDog = schema::UnionType::Make(R"gql(CatOrDog)gql"sv, R"md()md"sv); schema->AddType(R"gql(CatOrDog)gql"sv, typeCatOrDog); auto typeDogOrHuman = schema::UnionType::Make(R"gql(DogOrHuman)gql"sv, R"md()md"sv); schema->AddType(R"gql(DogOrHuman)gql"sv, typeDogOrHuman); auto typeHumanOrAlien = schema::UnionType::Make(R"gql(HumanOrAlien)gql"sv, R"md()md"sv); schema->AddType(R"gql(HumanOrAlien)gql"sv, typeHumanOrAlien); - auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"); + auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"sv); schema->AddType(R"gql(Query)gql"sv, typeQuery); - auto typeDog = schema::ObjectType::Make(R"gql(Dog)gql"sv, R"md()md"); + auto typeDog = schema::ObjectType::Make(R"gql(Dog)gql"sv, R"md()md"sv); schema->AddType(R"gql(Dog)gql"sv, typeDog); - auto typeAlien = schema::ObjectType::Make(R"gql(Alien)gql"sv, R"md()md"); + auto typeAlien = schema::ObjectType::Make(R"gql(Alien)gql"sv, R"md()md"sv); schema->AddType(R"gql(Alien)gql"sv, typeAlien); - auto typeHuman = schema::ObjectType::Make(R"gql(Human)gql"sv, R"md()md"); + auto typeHuman = schema::ObjectType::Make(R"gql(Human)gql"sv, R"md()md"sv); schema->AddType(R"gql(Human)gql"sv, typeHuman); - auto typeCat = schema::ObjectType::Make(R"gql(Cat)gql"sv, R"md()md"); + auto typeCat = schema::ObjectType::Make(R"gql(Cat)gql"sv, R"md()md"sv); schema->AddType(R"gql(Cat)gql"sv, typeCat); - auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"); + auto typeMutation = schema::ObjectType::Make(R"gql(Mutation)gql"sv, R"md()md"sv); schema->AddType(R"gql(Mutation)gql"sv, typeMutation); - auto typeMutateDogResult = schema::ObjectType::Make(R"gql(MutateDogResult)gql"sv, R"md()md"); + auto typeMutateDogResult = schema::ObjectType::Make(R"gql(MutateDogResult)gql"sv, R"md()md"sv); schema->AddType(R"gql(MutateDogResult)gql"sv, typeMutateDogResult); - auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md()md"); + auto typeSubscription = schema::ObjectType::Make(R"gql(Subscription)gql"sv, R"md()md"sv); schema->AddType(R"gql(Subscription)gql"sv, typeSubscription); - auto typeMessage = schema::ObjectType::Make(R"gql(Message)gql"sv, R"md()md"); + auto typeMessage = schema::ObjectType::Make(R"gql(Message)gql"sv, R"md()md"sv); schema->AddType(R"gql(Message)gql"sv, typeMessage); - auto typeArguments = schema::ObjectType::Make(R"gql(Arguments)gql"sv, R"md()md"); + auto typeArguments = schema::ObjectType::Make(R"gql(Arguments)gql"sv, R"md()md"sv); schema->AddType(R"gql(Arguments)gql"sv, typeArguments); typeDogCommand->AddEnumValues({ @@ -183,6 +186,8 @@ void AddTypesToSchema(const std::shared_ptr& schema) AddSentientDetails(typeSentient, schema); AddPetDetails(typePet, schema); + AddNodeDetails(typeNode, schema); + AddResourceDetails(typeResource, schema); AddCatOrDogDetails(typeCatOrDog, schema); AddDogOrHumanDetails(typeDogOrHuman, schema); @@ -211,7 +216,7 @@ std::shared_ptr GetSchema() if (!schema) { - schema = std::make_shared(true); + schema = std::make_shared(true, R"md()md"sv); introspection::AddTypesToSchema(schema); AddTypesToSchema(schema); s_wpSchema = schema; diff --git a/samples/validation/schema/ValidationSchema.h b/samples/validation/schema/ValidationSchema.h index 13375dc6..98d554ce 100644 --- a/samples/validation/schema/ValidationSchema.h +++ b/samples/validation/schema/ValidationSchema.h @@ -43,6 +43,8 @@ namespace object { class Sentient; class Pet; +class Node; +class Resource; class CatOrDog; class DogOrHuman; @@ -81,6 +83,8 @@ class Operations void AddSentientDetails(const std::shared_ptr& typeSentient, const std::shared_ptr& schema); void AddPetDetails(const std::shared_ptr& typePet, const std::shared_ptr& schema); +void AddNodeDetails(const std::shared_ptr& typeNode, const std::shared_ptr& schema); +void AddResourceDetails(const std::shared_ptr& typeResource, const std::shared_ptr& schema); void AddCatOrDogDetails(const std::shared_ptr& typeCatOrDog, const std::shared_ptr& schema); void AddDogOrHumanDetails(const std::shared_ptr& typeDogOrHuman, const std::shared_ptr& schema); diff --git a/samples/validation/schema/schema.validation.graphql b/samples/validation/schema/schema.validation.graphql index fe3b58ab..e1515c39 100644 --- a/samples/validation/schema/schema.validation.graphql +++ b/samples/validation/schema/schema.validation.graphql @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -"GraphQL validation [sample](http://spec.graphql.org/June2018/#example-26a9d)" +"GraphQL validation [sample](https://spec.graphql.org/October2021/#example-19f2a)" type Query { dog: Dog } @@ -32,6 +32,7 @@ type Alien implements Sentient { type Human implements Sentient { name: String! + pets: [Pet!]! } enum CatCommand { JUMP } @@ -47,43 +48,43 @@ union CatOrDog = Cat | Dog union DogOrHuman = Dog | Human union HumanOrAlien = Human | Alien -"Support for [Counter Example 94](http://spec.graphql.org/June2018/#example-77c2e)" +"Support for [Counter Example 105](https://spec.graphql.org/October2021/#example-77c2e)" type Mutation { mutateDog: MutateDogResult } -"Support for [Counter Example 94](http://spec.graphql.org/June2018/#example-77c2e)" +"Support for [Counter Example 105](https://spec.graphql.org/October2021/#example-77c2e)" type MutateDogResult { id: ID! } -"Support for [Example 97](http://spec.graphql.org/June2018/#example-5bbc3) - [Counter Example 101](http://spec.graphql.org/June2018/#example-2353b)" +"Support for [Example 108](https://spec.graphql.org/October2021/#example-5bbc3) - [Counter Example 112](https://spec.graphql.org/October2021/#example-a8fa1)" type Subscription { - "Support for [Example 97](http://spec.graphql.org/June2018/#example-5bbc3) - [Counter Example 101](http://spec.graphql.org/June2018/#example-2353b)" + "Support for [Example 108](https://spec.graphql.org/October2021/#example-5bbc3) - [Counter Example 112](https://spec.graphql.org/October2021/#example-a8fa1)" newMessage: Message! - "Support for [Counter Example 99](http://spec.graphql.org/June2018/#example-3997d) - [Counter Example 100](http://spec.graphql.org/June2018/#example-18466)" + "Support for [Counter Example 110](https://spec.graphql.org/October2021/#example-3997d) - [Counter Example 111](https://spec.graphql.org/October2021/#example-18466)" disallowedSecondRootField: Boolean! } -"Support for [Example 97](http://spec.graphql.org/June2018/#example-5bbc3) - [Counter Example 101](http://spec.graphql.org/June2018/#example-2353b)" +"Support for [Example 108](https://spec.graphql.org/October2021/#example-5bbc3) - [Counter Example 112](https://spec.graphql.org/October2021/#example-a8fa1)" type Message { body: String sender: ID! } -# http://spec.graphql.org/June2018/#example-9bada +# https://spec.graphql.org/October2021/#example-9bada extend type Query { - "Support for [Counter Example 116](http://spec.graphql.org/June2018/#example-77c2e)" + "Support for [Counter Example 127](https://spec.graphql.org/October2021/#example-77c2e)" human: Human - "Support for [Counter Example 116](http://spec.graphql.org/June2018/#example-77c2e)" + "Support for [Counter Example 127](https://spec.graphql.org/October2021/#example-77c2e)" pet: Pet - "Support for [Counter Example 116](http://spec.graphql.org/June2018/#example-77c2e)" + "Support for [Counter Example 127](https://spec.graphql.org/October2021/#example-77c2e)" catOrDog: CatOrDog } -"Support for [Example 120](http://spec.graphql.org/June2018/#example-1891c)" +"Support for [Example 131](https://spec.graphql.org/October2021/#example-73706)" type Arguments { - "Support for [Example 121](http://spec.graphql.org/June2018/#example-18fab)" + "Support for [Example 132](https://spec.graphql.org/October2021/#example-bda7e)" multipleReqs(x: Int!, y: Int!): Int! booleanArgField(booleanArg: Boolean): Boolean floatArgField(floatArg: Float): Float @@ -94,27 +95,38 @@ type Arguments { optionalNonNullBooleanArgField(optionalBooleanArg: Boolean! = false): Boolean! } -# http://spec.graphql.org/June2018/#example-1891c +# https://spec.graphql.org/October2021/#example-73706 extend type Query { - "Support for [Example 120](http://spec.graphql.org/June2018/#example-1891c)" + "Support for [Example 131](https://spec.graphql.org/October2021/#example-73706)" arguments: Arguments } -# http://spec.graphql.org/June2018/#example-6bbad -extend type Human { - "Support for [Counter Example 136](http://spec.graphql.org/June2018/#example-6bbad)" - pets: [Pet!]! +# https://spec.graphql.org/October2021/#example-bc12a +extend type Query { + "Support for [Example 156](https://spec.graphql.org/October2021/#example-bc12a)" + resource: Resource +} + +"Support for [Example 156](https://spec.graphql.org/October2021/#example-bc12a)" +interface Node { + id: ID! +} + +"Support for [Example 156](https://spec.graphql.org/October2021/#example-bc12a)" +interface Resource implements Node { + id: ID! + url: String } -"[Example 155](http://spec.graphql.org/June2018/#example-f3185)" +"[Example 167](https://spec.graphql.org/October2021/#example-ce150)" input ComplexInput { name: String owner: String } extend type Query { - "[Example 155](http://spec.graphql.org/June2018/#example-f3185)" + "[Example 167](https://spec.graphql.org/October2021/#example-ce150)" findDog(complex: ComplexInput): Dog - "[Example 155](http://spec.graphql.org/June2018/#example-f3185)" + "[Example 167](https://spec.graphql.org/October2021/#example-ce150)" booleanList(booleanListArg: [Boolean!]): Boolean } diff --git a/samples/validation/schema/validation_schema_files b/samples/validation/schema/validation_schema_files index fe72dfb8..60750fdd 100644 --- a/samples/validation/schema/validation_schema_files +++ b/samples/validation/schema/validation_schema_files @@ -1,6 +1,8 @@ ValidationSchema.cpp SentientObject.cpp PetObject.cpp +NodeObject.cpp +ResourceObject.cpp CatOrDogObject.cpp DogOrHumanObject.cpp HumanOrAlienObject.cpp diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 4c8e172b..68f154b9 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -164,7 +164,7 @@ response::Value schema_exception::getErrors() return buildErrorValues(std::move(_structuredErrors)); } -FieldParams::FieldParams(SelectionSetParams&& selectionSetParams, response::Value directives) +FieldParams::FieldParams(SelectionSetParams&& selectionSetParams, Directives directives) : SelectionSetParams(std::move(selectionSetParams)) , fieldDirectives(std::move(directives)) { @@ -337,27 +337,26 @@ class DirectiveVisitor void visit(const peg::ast_node& directives); bool shouldSkip() const; - response::Value getDirectives(); + Directives getDirectives(); private: const response::Value& _variables; - response::Value _directives; + Directives _directives; }; DirectiveVisitor::DirectiveVisitor(const response::Value& variables) : _variables(variables) - , _directives(response::Type::Map) { } void DirectiveVisitor::visit(const peg::ast_node& directives) { - response::Value result(response::Type::Map); + Directives result; for (const auto& directive : directives.children) { - std::string directiveName; + std::string_view directiveName; peg::on_first_child(*directive, [&directiveName](const peg::ast_node& child) { @@ -384,13 +383,13 @@ void DirectiveVisitor::visit(const peg::ast_node& directives) } }); - result.emplace_back(std::move(directiveName), std::move(directiveArguments)); + result.emplace_back(directiveName, std::move(directiveArguments)); } _directives = std::move(result); } -response::Value DirectiveVisitor::getDirectives() +Directives DirectiveVisitor::getDirectives() { auto result = std::move(_directives); @@ -399,15 +398,19 @@ response::Value DirectiveVisitor::getDirectives() bool DirectiveVisitor::shouldSkip() const { - static const std::array, 2> skippedNames = { - std::make_pair(true, "skip"), - std::make_pair(false, "include"), + constexpr std::array, 2> c_skippedDirectives = { + std::make_pair(true, R"gql(skip)gql"sv), + std::make_pair(false, R"gql(include)gql"sv), }; - for (const auto& entry : skippedNames) + for (const auto& entry : c_skippedDirectives) { const bool skip = entry.first; - auto itrDirective = _directives.find(entry.second); + auto itrDirective = std::find_if(_directives.cbegin(), + _directives.cend(), + [directiveName = entry.second](const auto& directive) noexcept { + return directive.first == directiveName; + }); if (itrDirective == _directives.end()) { @@ -468,7 +471,6 @@ bool DirectiveVisitor::shouldSkip() const Fragment::Fragment(const peg::ast_node& fragmentDefinition, const response::Value& variables) : _type(fragmentDefinition.children[1]->children.front()->string_view()) - , _directives(response::Type::Map) , _selection(*(fragmentDefinition.children.back())) { peg::on_first_child(fragmentDefinition, @@ -490,14 +492,14 @@ const peg::ast_node& Fragment::getSelection() const return _selection.get(); } -const response::Value& Fragment::getDirectives() const +const Directives& Fragment::getDirectives() const { return _directives; } ResolverParams::ResolverParams(const SelectionSetParams& selectionSetParams, const peg::ast_node& field, std::string&& fieldName, response::Value arguments, - response::Value fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, + Directives fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, const response::Value& variables) : SelectionSetParams(selectionSetParams) , field(field) @@ -591,7 +593,7 @@ response::IdType ModifiedArgument::convert(const response::Val void blockSubFields(const ResolverParams& params) { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + // https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections if (params.selection != nullptr) { auto position = params.selection->begin(); @@ -676,7 +678,7 @@ AwaitableResolver ModifiedResult::convert( void requireSubFields(const ResolverParams& params) { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + // https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections if (params.selection == nullptr) { auto position = params.field.begin(); @@ -713,17 +715,6 @@ AwaitableResolver ModifiedResult::convert( co_return std::move(document); } -// As we recursively expand fragment spreads and inline fragments, we want to accumulate the -// directives at each location and merge them with any directives included in outer fragments to -// build the complete set of directives for nested fragments. Directives with the same name at the -// same location will be overwritten by the innermost fragment. -struct FragmentDirectives -{ - response::Value fragmentDefinitionDirectives; - response::Value fragmentSpreadDirectives; - response::Value inlineFragmentDirectives; -}; - // SelectionVisitor visits the AST and resolves a field or fragment, unless it's skipped by // a directive or type condition. class SelectionVisitor @@ -744,7 +735,7 @@ class SelectionVisitor const ResolverContext _resolverContext; const std::shared_ptr& _state; - const response::Value& _operationDirectives; + const Directives& _operationDirectives; const std::optional> _path; const await_async _launch; const FragmentMap& _fragments; @@ -752,7 +743,9 @@ class SelectionVisitor const TypeNames& _typeNames; const ResolverMap& _resolvers; - std::list _fragmentDirectives; + std::shared_ptr _fragmentDefinitionDirectives; + std::shared_ptr _fragmentSpreadDirectives; + std::shared_ptr _inlineFragmentDirectives; internal::string_view_set _names; std::vector> _values; }; @@ -771,10 +764,18 @@ SelectionVisitor::SelectionVisitor(const SelectionSetParams& selectionSetParams, , _variables(variables) , _typeNames(typeNames) , _resolvers(resolvers) + , _fragmentDefinitionDirectives { selectionSetParams.fragmentDefinitionDirectives } + , _fragmentSpreadDirectives { selectionSetParams.fragmentSpreadDirectives } + , _inlineFragmentDirectives { selectionSetParams.inlineFragmentDirectives } { - _fragmentDirectives.push_back({ response::Value(response::Type::Map), - response::Value(response::Type::Map), - response::Value(response::Type::Map) }); + static const Directives s_emptyFragmentDefinitionDirectives; + + // Traversing a SelectionSet from an Object type field should start tracking new fragment + // directives. The outer fragment directives are still there in the FragmentSpreadDirectiveStack + // if the field accessors want to inspect them. + _fragmentDefinitionDirectives->push_front(std::cref(s_emptyFragmentDefinitionDirectives)); + _fragmentSpreadDirectives->push_front({}); + _inlineFragmentDirectives->push_front({}); _names.reserve(count); _values.reserve(count); @@ -883,9 +884,9 @@ void SelectionVisitor::visitField(const peg::ast_node& field) _resolverContext, _state, _operationDirectives, - _fragmentDirectives.back().fragmentDefinitionDirectives, - _fragmentDirectives.back().fragmentSpreadDirectives, - _fragmentDirectives.back().inlineFragmentDirectives, + _fragmentDefinitionDirectives, + _fragmentSpreadDirectives, + _inlineFragmentDirectives, std::make_optional(field_path { _path, path_segment { alias } }), _launch, }; @@ -978,33 +979,8 @@ void SelectionVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread) return; } - auto fragmentSpreadDirectives = directiveVisitor.getDirectives(); - - // Merge outer fragment spread directives as long as they don't conflict. - for (const auto& entry : _fragmentDirectives.back().fragmentSpreadDirectives) - { - if (fragmentSpreadDirectives.find(entry.first) == fragmentSpreadDirectives.end()) - { - fragmentSpreadDirectives.emplace_back(std::string { entry.first }, - response::Value(entry.second)); - } - } - - response::Value fragmentDefinitionDirectives(itr->second.getDirectives()); - - // Merge outer fragment definition directives as long as they don't conflict. - for (const auto& entry : _fragmentDirectives.back().fragmentDefinitionDirectives) - { - if (fragmentDefinitionDirectives.find(entry.first) == fragmentDefinitionDirectives.end()) - { - fragmentDefinitionDirectives.emplace_back(std::string { entry.first }, - response::Value(entry.second)); - } - } - - _fragmentDirectives.push_back({ std::move(fragmentDefinitionDirectives), - std::move(fragmentSpreadDirectives), - response::Value(_fragmentDirectives.back().inlineFragmentDirectives) }); + _fragmentDefinitionDirectives->push_front(itr->second.getDirectives()); + _fragmentSpreadDirectives->push_front(directiveVisitor.getDirectives()); const size_t count = itr->second.getSelection().children.size(); @@ -1019,7 +995,8 @@ void SelectionVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread) visit(*selection); } - _fragmentDirectives.pop_back(); + _fragmentSpreadDirectives->pop_front(); + _fragmentDefinitionDirectives->pop_front(); } void SelectionVisitor::visitInlineFragment(const peg::ast_node& inlineFragment) @@ -1048,23 +1025,7 @@ void SelectionVisitor::visitInlineFragment(const peg::ast_node& inlineFragment) { peg::on_first_child(inlineFragment, [this, &directiveVisitor](const peg::ast_node& child) { - auto inlineFragmentDirectives = directiveVisitor.getDirectives(); - - // Merge outer inline fragment directives as long as they don't conflict. - for (const auto& entry : _fragmentDirectives.back().inlineFragmentDirectives) - { - if (inlineFragmentDirectives.find(entry.first) - == inlineFragmentDirectives.end()) - { - inlineFragmentDirectives.emplace_back(std::string { entry.first }, - response::Value(entry.second)); - } - } - - _fragmentDirectives.push_back( - { response::Value(_fragmentDirectives.back().fragmentDefinitionDirectives), - response::Value(_fragmentDirectives.back().fragmentSpreadDirectives), - std::move(inlineFragmentDirectives) }); + _inlineFragmentDirectives->push_front(directiveVisitor.getDirectives()); const size_t count = child.children.size(); @@ -1079,7 +1040,7 @@ void SelectionVisitor::visitInlineFragment(const peg::ast_node& inlineFragment) visit(*selection); } - _fragmentDirectives.pop_back(); + _inlineFragmentDirectives->pop_front(); }); } } @@ -1179,7 +1140,7 @@ void Object::endSelectionSet(const SelectionSetParams&) const } OperationData::OperationData(std::shared_ptr state, response::Value variables, - response::Value directives, FragmentMap fragments) + Directives directives, FragmentMap fragments) : state(std::move(state)) , variables(std::move(variables)) , directives(std::move(directives)) @@ -1243,7 +1204,7 @@ class OperationDefinitionVisitor }; SubscriptionData::SubscriptionData(std::shared_ptr data, SubscriptionName&& field, - response::Value arguments, response::Value fieldDirectives, peg::ast&& query, + response::Value arguments, Directives fieldDirectives, peg::ast&& query, std::string&& operationName, SubscriptionCallback&& callback, const peg::ast_node& selection) : data(std::move(data)) , field(std::move(field)) @@ -1262,7 +1223,7 @@ OperationDefinitionVisitor::OperationDefinitionVisitor(ResolverContext resolverC : _resolverContext(resolverContext) , _launch(launch) , _params(std::make_shared( - std::move(state), std::move(variables), response::Value(), std::move(fragments))) + std::move(state), std::move(variables), Directives {}, std::move(fragments))) , _operations(operations) { } @@ -1321,7 +1282,7 @@ void OperationDefinitionVisitor::visit( _params->variables = std::move(operationVariables); - response::Value operationDirectives(response::Type::Map); + Directives operationDirectives; peg::on_first_child(operationDefinition, [this, &operationDirectives](const peg::ast_node& child) { @@ -1333,14 +1294,13 @@ void OperationDefinitionVisitor::visit( _params->directives = std::move(operationDirectives); - const response::Value emptyFragmentDirectives(response::Type::Map); const SelectionSetParams selectionSetParams { _resolverContext, _params->state, _params->directives, - emptyFragmentDirectives, - emptyFragmentDirectives, - emptyFragmentDirectives, + std::make_shared(), + std::make_shared(), + std::make_shared(), std::nullopt, _launch, }; @@ -1375,7 +1335,7 @@ class SubscriptionDefinitionVisitor const std::shared_ptr& _subscriptionObject; SubscriptionName _field; response::Value _arguments; - response::Value _fieldDirectives; + Directives _fieldDirectives; std::shared_ptr _result; }; @@ -1423,7 +1383,7 @@ void SubscriptionDefinitionVisitor::visit(const peg::ast_node& operationDefiniti } } - response::Value directives(response::Type::Map); + Directives directives; peg::on_first_child(operationDefinition, [this, &directives](const peg::ast_node& child) { @@ -1455,7 +1415,7 @@ void SubscriptionDefinitionVisitor::visitField(const peg::ast_node& field) name = child.string_view(); }); - // http://spec.graphql.org/June2018/#sec-Single-root-field + // https://spec.graphql.org/October2021/#sec-Single-root-field if (!_field.empty()) { auto position = field.begin(); @@ -1698,7 +1658,7 @@ response::AwaitableValue Request::resolve(await_async launch, std::shared_ptrsecond; const auto& registration = spThis->_subscriptions.at(key); - response::Value emptyFragmentDirectives(response::Type::Map); const SelectionSetParams selectionSetParams { ResolverContext::NotifySubscribe, registration->data->state, registration->data->directives, - emptyFragmentDirectives, - emptyFragmentDirectives, - emptyFragmentDirectives, + std::make_shared(), + std::make_shared(), + std::make_shared(), {}, launch, }; @@ -1886,14 +1845,13 @@ AwaitableUnsubscribe Request::unsubscribe(await_async launch, SubscriptionKey ke { const auto& operation = itrOperation->second; const auto& registration = spThis->_subscriptions.at(key); - response::Value emptyFragmentDirectives(response::Type::Map); const SelectionSetParams selectionSetParams { ResolverContext::NotifyUnsubscribe, registration->data->state, registration->data->directives, - emptyFragmentDirectives, - emptyFragmentDirectives, - emptyFragmentDirectives, + std::make_shared(), + std::make_shared(), + std::make_shared(), {}, launch, }; @@ -1923,22 +1881,22 @@ void Request::deliver(const SubscriptionName& name, const SubscriptionArguments& } void Request::deliver(const SubscriptionName& name, const SubscriptionArguments& arguments, - const SubscriptionArguments& directives, std::shared_ptr subscriptionObject) const + const Directives& directives, std::shared_ptr subscriptionObject) const { deliver(std::launch::deferred, name, arguments, directives, std::move(subscriptionObject)) .get(); } void Request::deliver(const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, + const SubscriptionArgumentFilterCallback& applyArguments, std::shared_ptr subscriptionObject) const { deliver(std::launch::deferred, name, applyArguments, std::move(subscriptionObject)).get(); } void Request::deliver(const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, - const SubscriptionFilterCallback& applyDirectives, + const SubscriptionArgumentFilterCallback& applyArguments, + const SubscriptionDirectiveFilterCallback& applyDirectives, std::shared_ptr subscriptionObject) const { deliver(std::launch::deferred, @@ -1955,34 +1913,34 @@ AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& na return deliver(launch, name, SubscriptionArguments {}, - SubscriptionArguments {}, + Directives {}, std::move(subscriptionObject)); } AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& name, const SubscriptionArguments& arguments, std::shared_ptr subscriptionObject) const { - return deliver(launch, - name, - arguments, - SubscriptionArguments {}, - std::move(subscriptionObject)); + return deliver(launch, name, arguments, Directives {}, std::move(subscriptionObject)); } AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& name, - const SubscriptionArguments& arguments, const SubscriptionArguments& directives, + const SubscriptionArguments& arguments, const Directives& directives, std::shared_ptr subscriptionObject) const { - SubscriptionFilterCallback argumentsMatch = + SubscriptionArgumentFilterCallback argumentsMatch = [&arguments](response::MapType::const_reference required) noexcept -> bool { auto itrArgument = arguments.find(required.first); return (itrArgument != arguments.end() && itrArgument->second == required.second); }; - SubscriptionFilterCallback directivesMatch = - [&directives](response::MapType::const_reference required) noexcept -> bool { - auto itrDirective = directives.find(required.first); + SubscriptionDirectiveFilterCallback directivesMatch = + [&directives](Directives::const_reference required) noexcept -> bool { + auto itrDirective = std::find_if(directives.cbegin(), + directives.cend(), + [directiveName = required.first](const auto& directive) noexcept { + return directive.first == directiveName; + }); return (itrDirective != directives.end() && itrDirective->second == required.second); }; @@ -1991,22 +1949,22 @@ AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& na } AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, + const SubscriptionArgumentFilterCallback& applyArguments, std::shared_ptr subscriptionObject) const { return deliver( launch, name, applyArguments, - [](response::MapType::const_reference) noexcept { + [](Directives::const_reference) noexcept { return true; }, std::move(subscriptionObject)); } AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& name, - const SubscriptionFilterCallback& applyArguments, - const SubscriptionFilterCallback& applyDirectives, + const SubscriptionArgumentFilterCallback& applyArguments, + const SubscriptionDirectiveFilterCallback& applyDirectives, std::shared_ptr subscriptionObject) const { const auto itrOperation = _operations.find(strSubscription); @@ -2085,14 +2043,13 @@ AwaitableDeliver Request::deliver(await_async launch, const SubscriptionName& na for (const auto& registration : registrations) { - response::Value emptyFragmentDirectives(response::Type::Map); const SelectionSetParams selectionSetParams { ResolverContext::Subscription, registration->data->state, registration->data->directives, - emptyFragmentDirectives, - emptyFragmentDirectives, - emptyFragmentDirectives, + std::make_shared(), + std::make_shared(), + std::make_shared(), std::nullopt, launch, }; diff --git a/src/Introspection.cpp b/src/Introspection.cpp index 963f1a2b..5ac539e9 100644 --- a/src/Introspection.cpp +++ b/src/Introspection.cpp @@ -5,12 +5,12 @@ #include "graphqlservice/introspection/IntrospectionSchema.h" -#include "graphqlservice/introspection/SchemaObject.h" -#include "graphqlservice/introspection/TypeObject.h" +#include "graphqlservice/introspection/DirectiveObject.h" +#include "graphqlservice/introspection/EnumValueObject.h" #include "graphqlservice/introspection/FieldObject.h" #include "graphqlservice/introspection/InputValueObject.h" -#include "graphqlservice/introspection/EnumValueObject.h" -#include "graphqlservice/introspection/DirectiveObject.h" +#include "graphqlservice/introspection/SchemaObject.h" +#include "graphqlservice/introspection/TypeObject.h" namespace graphql::introspection { @@ -19,6 +19,13 @@ Schema::Schema(const std::shared_ptr& schema) { } +std::optional Schema::getDescription() const +{ + const auto description = _schema->description(); + + return { description.empty() ? std::nullopt : std::make_optional(description) }; +} + std::vector> Schema::getTypes() const { const auto& types = _schema->types(); @@ -161,9 +168,16 @@ std::optional>> Type::getPossibleTypes possibleTypes.end(), result->begin(), [](const auto& entry) { - return std::make_shared(std::make_shared(entry.lock())); + auto typeEntry = entry.lock(); + + return typeEntry && typeEntry->kind() == introspection::TypeKind::OBJECT + ? std::make_shared(std::make_shared(std::move(typeEntry))) + : std::shared_ptr {}; }); + result->erase(std::remove(result->begin(), result->end(), std::shared_ptr {}), + result->cend()); + return result; } @@ -235,6 +249,14 @@ std::shared_ptr Type::getOfType() const return ofType ? std::make_shared(std::make_shared(ofType)) : nullptr; } +std::optional Type::getSpecifiedByURL() const +{ + const auto specifiedByURL = _type->specifiedByURL(); + + return { specifiedByURL.empty() ? std::nullopt + : std::make_optional(specifiedByURL) }; +} + Field::Field(const std::shared_ptr& field) : _field(field) { @@ -364,7 +386,7 @@ std::optional Directive::getDescription() const std::vector Directive::getLocations() const { - return { _directive->locations() }; + return _directive->locations(); } std::vector> Directive::getArgs() const @@ -379,4 +401,9 @@ std::vector> Directive::getArgs() const return result; } +bool Directive::getIsRepeatable() const +{ + return _directive->isRepeatable(); +} + } // namespace graphql::introspection diff --git a/src/JSONResponse.cpp b/src/JSONResponse.cpp index c0d9172d..f0608f77 100644 --- a/src/JSONResponse.cpp +++ b/src/JSONResponse.cpp @@ -138,7 +138,7 @@ struct ResponseHandler : rapidjson::BaseReaderHandler, Respons bool Int(int i) { - // http://spec.graphql.org/June2018/#sec-Int + // https://spec.graphql.org/October2021/#sec-Int static_assert(sizeof(i) == 4, "GraphQL only supports 32-bit signed integers"); auto value = Value(Type::Int); @@ -151,7 +151,7 @@ struct ResponseHandler : rapidjson::BaseReaderHandler, Respons { if (i > static_cast(std::numeric_limits::max())) { - // http://spec.graphql.org/June2018/#sec-Int + // https://spec.graphql.org/October2021/#sec-Int throw std::overflow_error("GraphQL only supports 32-bit signed integers"); } return Int(static_cast(i)); @@ -159,13 +159,13 @@ struct ResponseHandler : rapidjson::BaseReaderHandler, Respons bool Int64(int64_t /*i*/) { - // http://spec.graphql.org/June2018/#sec-Int + // https://spec.graphql.org/October2021/#sec-Int throw std::overflow_error("GraphQL only supports 32-bit signed integers"); } bool Uint64(uint64_t /*i*/) { - // http://spec.graphql.org/June2018/#sec-Int + // https://spec.graphql.org/October2021/#sec-Int throw std::overflow_error("GraphQL only supports 32-bit signed integers"); } diff --git a/src/RequestLoader.cpp b/src/RequestLoader.cpp index 2db44fb7..3ef113d7 100644 --- a/src/RequestLoader.cpp +++ b/src/RequestLoader.cpp @@ -276,7 +276,9 @@ void RequestLoader::addTypesToSchema() for (const auto& scalarType : _schemaLoader.getScalarTypes()) { _schema->AddType(scalarType.type, - schema::ScalarType::Make(scalarType.type, scalarType.description)); + schema::ScalarType::Make(scalarType.type, + scalarType.description, + scalarType.specifiedByURL)); } } @@ -542,7 +544,8 @@ void RequestLoader::addTypesToSchema() _schema->AddDirective(schema::Directive::Make(directive.name, directive.description, std::move(locations), - std::move(arguments))); + std::move(arguments), + directive.isRepeatable)); } for (const auto& operationType : _schemaLoader.getOperationTypes()) diff --git a/src/Schema.cpp b/src/Schema.cpp index 4541cab4..efe070ac 100644 --- a/src/Schema.cpp +++ b/src/Schema.cpp @@ -9,8 +9,9 @@ using namespace std::literals; namespace graphql::schema { -Schema::Schema(bool noIntrospection) +Schema::Schema(bool noIntrospection, std::string_view description) : _noIntrospection(noIntrospection) + , _description(description) { } @@ -88,6 +89,11 @@ void Schema::AddDirective(std::shared_ptr directive) _directives.emplace_back(std::move(directive)); } +std::string_view Schema::description() const noexcept +{ + return _description; +} + const std::vector>>& Schema::types() const noexcept { @@ -171,20 +177,28 @@ const std::weak_ptr& BaseType::ofType() const noexcept return defaultValue; } +std::string_view BaseType::specifiedByURL() const noexcept +{ + return ""sv; +} + struct ScalarType::init { std::string_view name; std::string_view description; + std::string_view specifiedByURL; }; -std::shared_ptr ScalarType::Make(std::string_view name, std::string_view description) +std::shared_ptr ScalarType::Make( + std::string_view name, std::string_view description, std::string_view specifiedByURL) { - return std::make_shared(init { name, description }); + return std::make_shared(init { name, description, specifiedByURL }); } ScalarType::ScalarType(init&& params) : BaseType(introspection::TypeKind::SCALAR, params.description) , _name(params.name) + , _specifiedByURL(params.specifiedByURL) { } @@ -193,6 +207,11 @@ std::string_view ScalarType::name() const noexcept return _name; } +std::string_view ScalarType::specifiedByURL() const noexcept +{ + return _specifiedByURL; +} + struct ObjectType::init { std::string_view name; @@ -216,8 +235,7 @@ void ObjectType::AddInterfaces(std::vector> for (const auto& interface : _interfaces) { - std::const_pointer_cast(interface)->AddPossibleType( - std::static_pointer_cast(shared_from_this())); + std::const_pointer_cast(interface)->AddPossibleType(shared_from_this()); } } @@ -259,11 +277,21 @@ InterfaceType::InterfaceType(init&& params) { } -void InterfaceType::AddPossibleType(std::weak_ptr possibleType) +void InterfaceType::AddPossibleType(std::weak_ptr possibleType) { _possibleTypes.push_back(possibleType); } +void InterfaceType::AddInterfaces(std::vector>&& interfaces) +{ + _interfaces = std::move(interfaces); + + for (const auto& interface : _interfaces) + { + std::const_pointer_cast(interface)->AddPossibleType(shared_from_this()); + } +} + void InterfaceType::AddFields(std::vector>&& fields) { _fields = std::move(fields); @@ -284,6 +312,11 @@ const std::vector>& InterfaceType::possibleTypes() return _possibleTypes; } +const std::vector>& InterfaceType::interfaces() const noexcept +{ + return _interfaces; +} + struct UnionType::init { std::string_view name; @@ -545,13 +578,14 @@ struct Directive::init std::string_view description; std::vector locations; std::vector> args; + bool isRepeatable; }; std::shared_ptr Directive::Make(std::string_view name, std::string_view description, std::vector&& locations, - std::vector>&& args) + std::vector>&& args, bool isRepeatable) { - init params { name, description, std::move(locations), std::move(args) }; + init params { name, description, std::move(locations), std::move(args), isRepeatable }; return std::make_shared(std::move(params)); } @@ -561,6 +595,7 @@ Directive::Directive(init&& params) , _description(params.description) , _locations(std::move(params.locations)) , _args(std::move(params.args)) + , _isRepeatable(params.isRepeatable) { } @@ -584,4 +619,9 @@ const std::vector>& Directive::args() const no return _args; } +bool Directive::isRepeatable() const noexcept +{ + return _isRepeatable; +} + } // namespace graphql::schema diff --git a/src/SchemaGenerator.cpp b/src/SchemaGenerator.cpp index f369a3c4..7425cb4e 100644 --- a/src/SchemaGenerator.cpp +++ b/src/SchemaGenerator.cpp @@ -1098,14 +1098,12 @@ bool Generator::outputSource() const noexcept if (_loader.isIntrospection()) { - sourceFile << R"cpp( -#include "graphqlservice/internal/Introspection.h" + sourceFile << R"cpp(#include "graphqlservice/internal/Introspection.h" )cpp"; } else { - sourceFile << R"cpp( -#include "graphqlservice/internal/Schema.h" + sourceFile << R"cpp(#include "graphqlservice/internal/Schema.h" #include "graphqlservice/introspection/IntrospectionSchema.h" )cpp"; @@ -1385,7 +1383,15 @@ Operations::Operations()cpp"; sourceFile << R"cpp(Built-in type)cpp"; } - sourceFile << R"cpp()md")); + sourceFile << R"cpp()md"sv, R"url()cpp"; + + if (!_options.noIntrospection) + { + sourceFile << R"cpp(https://spec.graphql.org/October2021/#sec-)cpp" + << builtinType.first; + } + + sourceFile << R"cpp()url"sv)); )cpp"; } } @@ -1403,7 +1409,14 @@ Operations::Operations()cpp"; sourceFile << scalarType.description; } - sourceFile << R"cpp()md")); + sourceFile << R"cpp()md", R"url()cpp"; + + if (!_options.noIntrospection) + { + sourceFile << scalarType.specifiedByURL; + } + + sourceFile << R"cpp()url"sv)); )cpp"; } } @@ -1504,7 +1517,7 @@ Operations::Operations()cpp"; sourceFile << objectType.description; } - sourceFile << R"cpp()md"); + sourceFile << R"cpp()md"sv); schema->AddType(R"gql()cpp" << objectType.type << R"cpp()gql"sv, type)cpp" << objectType.cppType << R"cpp(); @@ -1685,14 +1698,13 @@ Operations::Operations()cpp"; )cpp"; } - sourceFile << R"cpp(})cpp"; + sourceFile << R"cpp(}, {)cpp"; if (!directive.arguments.empty()) { bool firstArgument = true; - sourceFile << R"cpp(, { -)cpp"; + sourceFile << std::endl; for (const auto& argument : directive.arguments) { @@ -1718,9 +1730,10 @@ Operations::Operations()cpp"; } sourceFile << R"cpp( - })cpp"; + )cpp"; } - sourceFile << R"cpp()); + sourceFile << R"cpp(}, )cpp" + << (directive.isRepeatable ? R"cpp(true)cpp" : R"cpp(false)cpp") << R"cpp()); )cpp"; } } @@ -1755,7 +1768,15 @@ Operations::Operations()cpp"; if (!schema) { schema = std::make_shared()cpp" - << (_options.noIntrospection ? "true" : "false") << R"cpp(); + << (_options.noIntrospection ? R"cpp(true)cpp" : R"cpp(false)cpp") + << R"cpp(, R"md()cpp"; + + if (!_options.noIntrospection) + { + sourceFile << _loader.getSchemaDescription(); + } + + sourceFile << R"cpp()md"sv); )cpp" << SchemaLoader::getIntrospectionNamespace() << R"cpp(::AddTypesToSchema(schema); AddTypesToSchema(schema); @@ -1803,6 +1824,7 @@ void )cpp" << cppType void Generator::outputInterfaceIntrospection( std::ostream& sourceFile, const InterfaceType& interfaceType) const { + outputIntrospectionInterfaces(sourceFile, interfaceType.cppType, interfaceType.interfaces); outputIntrospectionFields(sourceFile, interfaceType.cppType, interfaceType.fields); } @@ -2149,14 +2171,21 @@ service::AwaitableResolver )cpp" void Generator::outputObjectIntrospection( std::ostream& sourceFile, const ObjectType& objectType) const { - if (!objectType.interfaces.empty()) + outputIntrospectionInterfaces(sourceFile, objectType.cppType, objectType.interfaces); + outputIntrospectionFields(sourceFile, objectType.cppType, objectType.fields); +} + +void Generator::outputIntrospectionInterfaces(std::ostream& sourceFile, std::string_view cppType, + const std::vector& interfaces) const +{ + if (!interfaces.empty()) { bool firstInterface = true; - sourceFile << R"cpp( type)cpp" << objectType.cppType << R"cpp(->AddInterfaces({ + sourceFile << R"cpp( type)cpp" << cppType << R"cpp(->AddInterfaces({ )cpp"; - for (const auto& interfaceName : objectType.interfaces) + for (const auto& interfaceName : interfaces) { if (!firstInterface) { @@ -2175,8 +2204,6 @@ void Generator::outputObjectIntrospection( }); )cpp"; } - - outputIntrospectionFields(sourceFile, objectType.cppType, objectType.fields); } void Generator::outputIntrospectionFields( diff --git a/src/SchemaLoader.cpp b/src/SchemaLoader.cpp index 3f6045e3..58453949 100644 --- a/src/SchemaLoader.cpp +++ b/src/SchemaLoader.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,9 @@ void SchemaLoader::validateSchema() // Handle nested input types by fully declaring the dependencies first. reorderInputTypeDependencies(); + // Validate the interface dependencies and that all of the interface fields are implemented. + validateImplementedInterfaces(); + for (auto& entry : _interfaceTypes) { fixupOutputFieldList(entry.fields, std::nullopt, std::nullopt); @@ -186,30 +190,6 @@ void SchemaLoader::validateSchema() fixupOutputFieldList(entry.fields, interfaceFields, accessor); } - // Validate the interfaces implemented by the object types. - for (const auto& entry : _objectTypes) - { - for (const auto& interfaceName : entry.interfaces) - { - if (_interfaceNames.find(interfaceName) == _interfaceNames.cend()) - { - std::ostringstream error; - auto itrPosition = _typePositions.find(entry.type); - - error << "Unknown interface: " << interfaceName - << " implemented by: " << entry.type; - - if (itrPosition != _typePositions.cend()) - { - error << " line: " << itrPosition->second.line - << " column: " << itrPosition->second.column; - } - - throw std::runtime_error(error.str()); - } - } - } - // Validate the objects that are possible types for unions and add the unions to // the list of matching types for the objects. for (const auto& entry : _unionTypes) @@ -433,6 +413,29 @@ void SchemaLoader::reorderInputTypeDependencies() } } +void SchemaLoader::validateImplementedInterfaces() const +{ + for (const auto& interfaceType : _interfaceTypes) + { + validateTransitiveInterfaces(interfaceType.type, interfaceType.interfaces); + + for (auto interfaceName : interfaceType.interfaces) + { + validateInterfaceFields(interfaceType.type, interfaceName, interfaceType.fields); + } + } + + for (const auto& objectType : _objectTypes) + { + validateTransitiveInterfaces(objectType.type, objectType.interfaces); + + for (auto interfaceName : objectType.interfaces) + { + validateInterfaceFields(objectType.type, interfaceName, objectType.fields); + } + } +} + void SchemaLoader::visitDefinition(const peg::ast_node& definition) { if (definition.is_type()) @@ -505,6 +508,21 @@ void SchemaLoader::visitDefinition(const peg::ast_node& definition) void SchemaLoader::visitSchemaDefinition(const peg::ast_node& schemaDefinition) { + std::string_view description; + + peg::on_first_child(schemaDefinition, + [&description](const peg::ast_node& child) { + if (!child.children.empty()) + { + description = child.children.front()->unescaped_view(); + } + }); + + if (_schemaDescription.empty()) + { + _schemaDescription = description; + } + peg::for_each_child(schemaDefinition, [this](const peg::ast_node& child) { const auto operation(child.children.front()->string_view()); @@ -533,8 +551,12 @@ void SchemaLoader::visitObjectTypeDefinition(const peg::ast_node& objectTypeDefi std::string_view description; peg::on_first_child(objectTypeDefinition, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); peg::on_first_child(objectTypeDefinition, @@ -560,9 +582,14 @@ void SchemaLoader::visitObjectTypeExtension(const peg::ast_node& objectTypeExten { std::string_view name; - peg::on_first_child(objectTypeExtension, [&name](const peg::ast_node& child) { - name = child.string_view(); - }); + peg::on_first_child(objectTypeExtension, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); const auto itrType = _objectNames.find(name); @@ -585,6 +612,18 @@ void SchemaLoader::visitObjectTypeExtension(const peg::ast_node& objectTypeExten objectType.fields.push_back(std::move(field)); } }); + + if (!_isIntrospection) + { + for (const auto& field : objectType.fields) + { + blockReservedName(field.name, field.position); + for (const auto& argument : field.arguments) + { + blockReservedName(argument.name, argument.position); + } + } + } } } @@ -594,8 +633,12 @@ void SchemaLoader::visitInterfaceTypeDefinition(const peg::ast_node& interfaceTy std::string_view description; peg::on_first_child(interfaceTypeDefinition, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); peg::on_first_child(interfaceTypeDefinition, @@ -612,7 +655,7 @@ void SchemaLoader::visitInterfaceTypeDefinition(const peg::ast_node& interfaceTy auto cppName = getSafeCppName(name); - _interfaceTypes.push_back({ name, cppName, {}, description }); + _interfaceTypes.push_back({ name, cppName, {}, {}, description }); visitInterfaceTypeExtension(interfaceTypeDefinition); } @@ -622,8 +665,12 @@ void SchemaLoader::visitInterfaceTypeExtension(const peg::ast_node& interfaceTyp std::string_view name; peg::on_first_child(interfaceTypeExtension, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); const auto itrType = _interfaceNames.find(name); @@ -632,6 +679,11 @@ void SchemaLoader::visitInterfaceTypeExtension(const peg::ast_node& interfaceTyp { auto& interfaceType = _interfaceTypes[itrType->second]; + peg::for_each_child(interfaceTypeExtension, + [&interfaceType](const peg::ast_node& child) { + interfaceType.interfaces.push_back(child.string_view()); + }); + peg::on_first_child(interfaceTypeExtension, [&interfaceType](const peg::ast_node& child) { auto fields = getOutputFields(child.children); @@ -642,6 +694,18 @@ void SchemaLoader::visitInterfaceTypeExtension(const peg::ast_node& interfaceTyp interfaceType.fields.push_back(std::move(field)); } }); + + if (!_isIntrospection) + { + for (const auto& field : interfaceType.fields) + { + blockReservedName(field.name, field.position); + for (const auto& argument : field.arguments) + { + blockReservedName(argument.name, argument.position); + } + } + } } } @@ -651,8 +715,12 @@ void SchemaLoader::visitInputObjectTypeDefinition(const peg::ast_node& inputObje std::string_view description; peg::on_first_child(inputObjectTypeDefinition, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); peg::on_first_child(inputObjectTypeDefinition, @@ -679,8 +747,12 @@ void SchemaLoader::visitInputObjectTypeExtension(const peg::ast_node& inputObjec std::string_view name; peg::on_first_child(inputObjectTypeExtension, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); const auto itrType = _inputNames.find(name); @@ -699,6 +771,14 @@ void SchemaLoader::visitInputObjectTypeExtension(const peg::ast_node& inputObjec inputType.fields.push_back(std::move(field)); } }); + + if (!_isIntrospection) + { + for (const auto& field : inputType.fields) + { + blockReservedName(field.name, field.position); + } + } } } @@ -707,9 +787,14 @@ void SchemaLoader::visitEnumTypeDefinition(const peg::ast_node& enumTypeDefiniti std::string_view name; std::string_view description; - peg::on_first_child(enumTypeDefinition, [&name](const peg::ast_node& child) { - name = child.string_view(); - }); + peg::on_first_child(enumTypeDefinition, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); peg::on_first_child(enumTypeDefinition, [&description](const peg::ast_node& child) { @@ -734,9 +819,14 @@ void SchemaLoader::visitEnumTypeExtension(const peg::ast_node& enumTypeExtension { std::string_view name; - peg::on_first_child(enumTypeExtension, [&name](const peg::ast_node& child) { - name = child.string_view(); - }); + peg::on_first_child(enumTypeExtension, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); const auto itrType = _enumNames.find(name); @@ -771,7 +861,7 @@ void SchemaLoader::visitEnumTypeExtension(const peg::ast_node& enumTypeExtension directiveName = name.string_view(); }); - if (directiveName == "deprecated") + if (directiveName == "deprecated"sv) { std::string_view reason; @@ -786,7 +876,7 @@ void SchemaLoader::visitEnumTypeExtension(const peg::ast_node& enumTypeExtension argumentName = name.string_view(); }); - if (argumentName == "reason") + if (argumentName == "reason"sv) { peg::on_first_child(argument, [&value](const peg::ast_node& argumentValue) { @@ -812,8 +902,12 @@ void SchemaLoader::visitScalarTypeDefinition(const peg::ast_node& scalarTypeDefi std::string_view description; peg::on_first_child(scalarTypeDefinition, - [&name](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } }); peg::on_first_child(scalarTypeDefinition, @@ -828,6 +922,70 @@ void SchemaLoader::visitScalarTypeDefinition(const peg::ast_node& scalarTypeDefi _typePositions.emplace(name, scalarTypeDefinition.begin()); _scalarNames[name] = _scalarTypes.size(); _scalarTypes.push_back({ name, description }); + + visitScalarTypeExtension(scalarTypeDefinition); +} + +void SchemaLoader::visitScalarTypeExtension(const peg::ast_node& scalarTypeExtension) +{ + std::string_view name; + + peg::on_first_child(scalarTypeExtension, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); + + const auto itrType = _scalarNames.find(name); + + if (itrType != _scalarNames.cend()) + { + auto& scalarType = _scalarTypes[itrType->second]; + + peg::on_first_child(scalarTypeExtension, + [&scalarType](const peg::ast_node& directives) { + peg::for_each_child(directives, + [&scalarType](const peg::ast_node& directive) { + std::string_view directiveName; + + peg::on_first_child(directive, + [&directiveName](const peg::ast_node& name) { + directiveName = name.string_view(); + }); + + if (directiveName == "specifiedBy"sv) + { + std::string_view specifiedByURL; + + peg::on_first_child(directive, + [&specifiedByURL](const peg::ast_node& arguments) { + peg::on_first_child(arguments, + [&specifiedByURL](const peg::ast_node& argument) { + std::string_view argumentName; + + peg::on_first_child(argument, + [&argumentName](const peg::ast_node& name) { + argumentName = name.string_view(); + }); + + if (argumentName == "url"sv) + { + peg::on_first_child(argument, + [&specifiedByURL](const peg::ast_node& url) { + specifiedByURL = url.unescaped_view(); + }); + } + }); + }); + + scalarType.specifiedByURL = std::move(specifiedByURL); + } + }); + }); + } } void SchemaLoader::visitUnionTypeDefinition(const peg::ast_node& unionTypeDefinition) @@ -835,9 +993,14 @@ void SchemaLoader::visitUnionTypeDefinition(const peg::ast_node& unionTypeDefini std::string_view name; std::string_view description; - peg::on_first_child(unionTypeDefinition, [&name](const peg::ast_node& child) { - name = child.string_view(); - }); + peg::on_first_child(unionTypeDefinition, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); peg::on_first_child(unionTypeDefinition, [&description](const peg::ast_node& child) { @@ -862,9 +1025,14 @@ void SchemaLoader::visitUnionTypeExtension(const peg::ast_node& unionTypeExtensi { std::string_view name; - peg::on_first_child(unionTypeExtension, [&name](const peg::ast_node& child) { - name = child.string_view(); - }); + peg::on_first_child(unionTypeExtension, + [isIntrospection = _isIntrospection, &name](const peg::ast_node& child) { + name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(name, child.begin()); + } + }); const auto itrType = _unionNames.find(name); @@ -884,8 +1052,12 @@ void SchemaLoader::visitDirectiveDefinition(const peg::ast_node& directiveDefini Directive directive; peg::on_first_child(directiveDefinition, - [&directive](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &directive](const peg::ast_node& child) { directive.name = child.string_view(); + if (!isIntrospection) + { + blockReservedName(directive.name, child.begin()); + } }); peg::on_first_child(directiveDefinition, @@ -896,18 +1068,27 @@ void SchemaLoader::visitDirectiveDefinition(const peg::ast_node& directiveDefini } }); + peg::on_first_child(directiveDefinition, + [&directive](const peg::ast_node& child) { + directive.isRepeatable = true; + }); + peg::for_each_child(directiveDefinition, [&directive](const peg::ast_node& child) { directive.locations.push_back(child.string_view()); }); peg::on_first_child(directiveDefinition, - [&directive](const peg::ast_node& child) { + [isIntrospection = _isIntrospection, &directive](const peg::ast_node& child) { auto fields = getInputFields(child.children); directive.arguments.reserve(directive.arguments.size() + fields.size()); for (auto& field : fields) { + if (!isIntrospection) + { + blockReservedName(field.name, field.position); + } directive.arguments.push_back(std::move(field)); } }); @@ -951,6 +1132,148 @@ std::string_view SchemaLoader::getSafeCppName(std::string_view type) noexcept return (safeNames.cend() == itr) ? type : itr->second->second; } +void SchemaLoader::blockReservedName( + std::string_view name, std::optional position) +{ + // https://spec.graphql.org/October2021/#sec-Names.Reserved-Names + if (name.size() > 1 && name.substr(0, 2) == R"gql(__)gql"sv) + { + std::ostringstream error; + + error << "Names starting with __ are reserved: " << name; + + if (position) + { + error << " line: " << position->line << " column: " << position->column; + } + + throw std::runtime_error(error.str()); + } +} + +const InterfaceType& SchemaLoader::findInterfaceType( + std::string_view typeName, std::string_view interfaceName) const +{ + const auto itrType = _interfaceNames.find(interfaceName); + + if (itrType == _interfaceNames.cend()) + { + std::ostringstream error; + const auto itrPosition = _typePositions.find(typeName); + + error << "Unknown interface: " << interfaceName << " implemented by: " << typeName; + + if (itrPosition != _typePositions.cend()) + { + error << " line: " << itrPosition->second.line + << " column: " << itrPosition->second.column; + } + + throw std::runtime_error(error.str()); + } + + return _interfaceTypes[itrType->second]; +} + +void SchemaLoader::validateInterfaceFields(std::string_view typeName, + std::string_view interfaceName, const OutputFieldList& typeFields) const +{ + const auto& interfaceType = findInterfaceType(typeName, interfaceName); + std::set unimplemented; + + for (const auto& entry : interfaceType.fields) + { + unimplemented.insert(entry.name); + } + + for (const auto& entry : typeFields) + { + unimplemented.erase(entry.name); + } + + if (!unimplemented.empty()) + { + std::ostringstream error; + const auto itrPosition = _typePositions.find(typeName); + + error << "Missing interface fields type: " << typeName + << " interface: " << interfaceType.type; + + if (itrPosition != _typePositions.cend()) + { + error << " line: " << itrPosition->second.line + << " column: " << itrPosition->second.column; + } + + for (auto fieldName : unimplemented) + { + error << " field: " << fieldName; + } + + throw std::runtime_error(error.str()); + } +} + +void SchemaLoader::validateTransitiveInterfaces( + std::string_view typeName, const std::vector& interfaces) const +{ + std::set unimplemented; + + for (auto entry : interfaces) + { + const auto& interfaceType = findInterfaceType(typeName, entry); + + unimplemented.insert(entry); + + for (auto interfaceName : interfaceType.interfaces) + { + unimplemented.insert(interfaceName); + } + } + + if (unimplemented.find(typeName) != unimplemented.cend()) + { + std::ostringstream error; + const auto itrPosition = _typePositions.find(typeName); + + error << "Interface cycle interface: " << typeName; + + if (itrPosition != _typePositions.cend()) + { + error << " line: " << itrPosition->second.line + << " column: " << itrPosition->second.column; + } + + throw std::runtime_error(error.str()); + } + + for (auto entry : interfaces) + { + unimplemented.erase(entry); + } + + if (!unimplemented.empty()) + { + std::ostringstream error; + const auto itrPosition = _typePositions.find(typeName); + + error << "Missing transitive interface type: " << typeName; + + if (itrPosition != _typePositions.cend()) + { + error << " line: " << itrPosition->second.line + << " column: " << itrPosition->second.column; + } + + for (auto interfaceName : unimplemented) + { + error << " interface: " << interfaceName; + } + + throw std::runtime_error(error.str()); + } +} + OutputFieldList SchemaLoader::getOutputFields(const peg::ast_node::children_t& fields) { OutputFieldList outputFields; @@ -991,7 +1314,7 @@ OutputFieldList SchemaLoader::getOutputFields(const peg::ast_node::children_t& f directiveName = name.string_view(); }); - if (directiveName == "deprecated") + if (directiveName == "deprecated"sv) { std::string_view deprecationReason; @@ -1006,7 +1329,7 @@ OutputFieldList SchemaLoader::getOutputFields(const peg::ast_node::children_t& f argumentName = name.string_view(); }); - if (argumentName == "reason") + if (argumentName == "reason"sv) { peg::on_first_child(argument, [&deprecationReason]( @@ -1097,6 +1420,11 @@ bool SchemaLoader::isIntrospection() const noexcept return _isIntrospection; } +std::string_view SchemaLoader::getSchemaDescription() const noexcept +{ + return _schemaDescription; +} + std::string_view SchemaLoader::getFilenamePrefix() const noexcept { return _schemaOptions.filenamePrefix; diff --git a/src/SyntaxTree.cpp b/src/SyntaxTree.cpp index b323f54c..e7cad6a5 100644 --- a/src/SyntaxTree.cpp +++ b/src/SyntaxTree.cpp @@ -9,9 +9,12 @@ #include #include +#include #include #include +#include #include +#include using namespace std::literals; @@ -47,7 +50,90 @@ std::string_view ast_node::unescaped_view() const { if (!_unescaped) { - if (children.size() > 1) + if (is_type()) + { + // Trim leading and trailing empty lines + const auto isNonEmptyLine = [](const std::unique_ptr& child) noexcept { + return child->is_type(); + }; + const auto itrEndRev = std::make_reverse_iterator( + std::find_if(children.cbegin(), children.cend(), isNonEmptyLine)); + const auto itrRev = std::find_if(children.crbegin(), itrEndRev, isNonEmptyLine); + std::vector>> lines( + std::distance(itrRev, itrEndRev)); + + std::transform(itrRev, + itrEndRev, + lines.rbegin(), + [](const std::unique_ptr& child) noexcept { + return (child->is_type() && !child->children.empty() + && child->children.front()->is_type() + && child->children.back()->is_type()) + ? std::make_optional(std::make_pair(child->children.front()->string_view(), + child->children.back()->unescaped_view())) + : std::nullopt; + }); + + // Calculate the common indent + const auto commonIndent = std::accumulate(lines.cbegin(), + lines.cend(), + std::optional {}, + [](auto value, const auto& line) noexcept { + if (line) + { + const auto indent = line->first.size(); + + if (!value || indent < *value) + { + value = indent; + } + } + + return value; + }); + + const auto trimIndent = commonIndent ? *commonIndent : 0; + std::string joined; + + if (!lines.empty()) + { + joined.reserve(std::accumulate(lines.cbegin(), + lines.cend(), + size_t {}, + [trimIndent](auto value, const auto& line) noexcept { + if (line) + { + value += line->first.size() - trimIndent; + value += line->second.size(); + } + + return value; + }) + + lines.size() - 1); + + bool firstLine = true; + + for (const auto& line : lines) + { + if (!firstLine) + { + joined.append(1, '\n'); + } + + if (line) + { + joined.append(line->first.substr(trimIndent)); + joined.append(line->second); + } + + firstLine = false; + } + } + + const_cast(this)->_unescaped = + std::make_unique(std::move(joined)); + } + else if (children.size() > 1) { std::string joined; @@ -224,6 +310,26 @@ struct ast_selector : std::true_type } }; +template <> +struct ast_selector : std::true_type +{ +}; + +template <> +struct ast_selector : std::true_type +{ +}; + +template <> +struct ast_selector : std::true_type +{ +}; + +template <> +struct ast_selector : std::true_type +{ +}; + template <> struct ast_selector : std::true_type { @@ -483,6 +589,11 @@ struct schema_selector : std::true_type { }; +template <> +struct schema_selector : std::true_type +{ +}; + template <> struct schema_selector : std::true_type { @@ -573,164 +684,164 @@ const std::string ast_control::error_message = "Expected \"\" template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Variable"; + "Expected https://spec.graphql.org/October2021/#Variable"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EscapedUnicode"; + "Expected https://spec.graphql.org/October2021/#EscapedUnicode"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EscapedCharacter"; + "Expected https://spec.graphql.org/October2021/#EscapedCharacter"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#StringCharacter"; + "Expected https://spec.graphql.org/October2021/#StringCharacter"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#BlockStringCharacter"; + "Expected https://spec.graphql.org/October2021/#BlockStringCharacter"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#FractionalPart"; + "Expected https://spec.graphql.org/October2021/#FractionalPart"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ExponentPart"; + "Expected https://spec.graphql.org/October2021/#ExponentPart"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Argument"; + "Expected https://spec.graphql.org/October2021/#Argument"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Arguments"; + "Expected https://spec.graphql.org/October2021/#Arguments"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ListValue"; + "Expected https://spec.graphql.org/October2021/#ListValue"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ObjectField"; + "Expected https://spec.graphql.org/October2021/#ObjectField"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ObjectValue"; + "Expected https://spec.graphql.org/October2021/#ObjectValue"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Value"; + "Expected https://spec.graphql.org/October2021/#Value"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#DefaultValue"; + "Expected https://spec.graphql.org/October2021/#DefaultValue"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ListType"; + "Expected https://spec.graphql.org/October2021/#ListType"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Type"; + "Expected https://spec.graphql.org/October2021/#Type"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#VariableDefinition"; + "Expected https://spec.graphql.org/October2021/#VariableDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#VariableDefinitions"; + "Expected https://spec.graphql.org/October2021/#VariableDefinitions"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Directive"; + "Expected https://spec.graphql.org/October2021/#Directive"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Field"; + "Expected https://spec.graphql.org/October2021/#Field"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#TypeCondition"; + "Expected https://spec.graphql.org/October2021/#TypeCondition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#FragmentSpread or " - "http://spec.graphql.org/June2018/#InlineFragment"; + "Expected https://spec.graphql.org/October2021/#FragmentSpread or " + "https://spec.graphql.org/October2021/#InlineFragment"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#SelectionSet"; + "Expected https://spec.graphql.org/October2021/#SelectionSet"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#OperationDefinition"; + "Expected https://spec.graphql.org/October2021/#OperationDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#FragmentDefinition"; + "Expected https://spec.graphql.org/October2021/#FragmentDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#RootOperationTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#RootOperationTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#SchemaDefinition"; + "Expected https://spec.graphql.org/October2021/#SchemaDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ScalarTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#ScalarTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ArgumentsDefinition"; + "Expected https://spec.graphql.org/October2021/#ArgumentsDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#FieldDefinition"; + "Expected https://spec.graphql.org/October2021/#FieldDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#FieldsDefinition"; + "Expected https://spec.graphql.org/October2021/#FieldsDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ImplementsInterfaces"; + "Expected https://spec.graphql.org/October2021/#ImplementsInterfaces"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ObjectTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#ObjectTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InterfaceTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#InterfaceTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#UnionMemberTypes"; + "Expected https://spec.graphql.org/October2021/#UnionMemberTypes"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#UnionTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#UnionTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EnumValueDefinition"; + "Expected https://spec.graphql.org/October2021/#EnumValueDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EnumValuesDefinition"; + "Expected https://spec.graphql.org/October2021/#EnumValuesDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EnumTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#EnumTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InputValueDefinition"; + "Expected https://spec.graphql.org/October2021/#InputValueDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InputFieldsDefinition"; + "Expected https://spec.graphql.org/October2021/#InputFieldsDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InputObjectTypeDefinition"; + "Expected https://spec.graphql.org/October2021/#InputObjectTypeDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#DirectiveDefinition"; + "Expected https://spec.graphql.org/October2021/#DirectiveDefinition"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#SchemaExtension"; + "Expected https://spec.graphql.org/October2021/#SchemaExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ScalarTypeExtension"; + "Expected https://spec.graphql.org/October2021/#ScalarTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#ObjectTypeExtension"; + "Expected https://spec.graphql.org/October2021/#ObjectTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InterfaceTypeExtension"; + "Expected https://spec.graphql.org/October2021/#InterfaceTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#UnionTypeExtension"; + "Expected https://spec.graphql.org/October2021/#UnionTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#EnumTypeExtension"; + "Expected https://spec.graphql.org/October2021/#EnumTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#InputObjectTypeExtension"; + "Expected https://spec.graphql.org/October2021/#InputObjectTypeExtension"; template <> const std::string ast_control::error_message = - "Expected http://spec.graphql.org/June2018/#Document"; + "Expected https://spec.graphql.org/October2021/#Document"; template <> const std::string ast_control::error_message = - "Expected executable http://spec.graphql.org/June2018/#Document"; + "Expected executable https://spec.graphql.org/October2021/#Document"; template <> const std::string ast_control::error_message = - "Expected schema type http://spec.graphql.org/June2018/#Document"; + "Expected schema type https://spec.graphql.org/October2021/#Document"; ast parseSchemaString(std::string_view input) { diff --git a/src/Validation.cpp b/src/Validation.cpp index e51c8a0f..79197c74 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -243,7 +243,7 @@ void ValidateArgumentValueVisitor::visitObjectValue(const peg::ast_node& objectV if (value.values.find(name) != value.values.end()) { - // http://spec.graphql.org/June2018/#sec-Input-Object-Field-Uniqueness + // https://spec.graphql.org/October2021/#sec-Input-Object-Field-Uniqueness auto position = field->begin(); std::ostringstream message; @@ -403,7 +403,15 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(std::shared_ptrpossibleTypes(); - matchingTypes.reserve(possibleTypes.size()); + if (kind == introspection::TypeKind::INTERFACE) + { + matchingTypes.reserve(possibleTypes.size() + 1); + matchingTypes.emplace(name); + } + else + { + matchingTypes.reserve(possibleTypes.size()); + } for (const auto& possibleType : possibleTypes) { @@ -460,6 +468,8 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(std::shared_ptrargs(); ValidateDirective validateDirective; + validateDirective.isRepeatable = directive->isRepeatable(); + for (const auto location : locations) { validateDirective.locations.emplace(location); @@ -481,7 +491,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) if (!inserted.second) { - // http://spec.graphql.org/June2018/#sec-Fragment-Name-Uniqueness + // https://spec.graphql.org/October2021/#sec-Fragment-Name-Uniqueness auto position = fragmentDefinition.begin(); std::ostringstream error; @@ -505,7 +515,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) if (!inserted.second) { - // http://spec.graphql.org/June2018/#sec-Operation-Name-Uniqueness + // https://spec.graphql.org/October2021/#sec-Operation-Name-Uniqueness auto position = operationDefinition.begin(); std::ostringstream error; @@ -526,7 +536,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) if (itr != _operationDefinitions.end()) { - // http://spec.graphql.org/June2018/#sec-Lone-Anonymous-Operation + // https://spec.graphql.org/October2021/#sec-Lone-Anonymous-Operation auto position = itr->second.get().begin(); _errors.push_back( @@ -547,7 +557,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) } else { - // http://spec.graphql.org/June2018/#sec-Executable-Definitions + // https://spec.graphql.org/October2021/#sec-Executable-Definitions auto position = child->begin(); _errors.push_back({ "Unexpected type definition", { position.line, position.column } }); @@ -556,7 +566,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) if (!_fragmentDefinitions.empty()) { - // http://spec.graphql.org/June2018/#sec-Fragments-Must-Be-Used + // https://spec.graphql.org/October2021/#sec-Fragments-Must-Be-Used const size_t originalSize = _errors.size(); auto unreferencedFragments = std::move(_fragmentDefinitions); @@ -607,8 +617,8 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra if (itrType == _types.end() || isScalarType(itrType->second->get().kind())) { - // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence - // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types + // https://spec.graphql.org/October2021/#sec-Fragment-Spread-Type-Existence + // https://spec.graphql.org/October2021/#sec-Fragments-On-Composite-Types auto position = typeCondition->begin(); std::ostringstream message; @@ -662,7 +672,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op if (_operationVariables->find(variableName) != _operationVariables->end()) { - // http://spec.graphql.org/June2018/#sec-Variable-Uniqueness + // https://spec.graphql.org/October2021/#sec-Variable-Uniqueness auto position = child->begin(); std::ostringstream message; @@ -688,7 +698,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op if (!visitor.isInputType()) { - // http://spec.graphql.org/June2018/#sec-Variables-Are-Input-Types + // https://spec.graphql.org/October2021/#sec-Variables-Are-Input-Types auto position = child->begin(); std::ostringstream message; @@ -717,7 +727,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op if (!validateInputValue(false, argument, variableArgument.type)) { - // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type + // https://spec.graphql.org/October2021/#sec-Values-of-Correct-Type auto position = child->begin(); std::ostringstream message; @@ -773,26 +783,46 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op } _scopedType = itrType->second; + _introspectionFieldCount = 0; _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); visitSelection(selection); - if (_fieldCount > 1 && operationType == strSubscription) + if (operationType == strSubscription) { - // http://spec.graphql.org/June2018/#sec-Single-root-field - auto position = operationDefinition.begin(); - std::ostringstream error; + if (_fieldCount > 1) + { + // https://spec.graphql.org/October2021/#sec-Single-root-field + auto position = operationDefinition.begin(); + std::ostringstream error; - error << "Subscription with more than one root field"; + error << "Subscription with more than one root field"; - if (!operationName.empty()) - { - error << " name: " << operationName; + if (!operationName.empty()) + { + error << " name: " << operationName; + } + + _errors.push_back({ error.str(), { position.line, position.column } }); } - _errors.push_back({ error.str(), { position.line, position.column } }); + if (_introspectionFieldCount != 0) + { + // https://spec.graphql.org/October2021/#sec-Single-root-field + auto position = operationDefinition.begin(); + std::ostringstream error; + + error << "Subscription with Introspection root field"; + + if (!operationName.empty()) + { + error << " name: " << operationName; + } + + _errors.push_back({ error.str(), { position.line, position.column } }); + } } _scopedType.reset(); @@ -803,7 +833,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op { if (_referencedVariables.find(variable.first) == _referencedVariables.end()) { - // http://spec.graphql.org/June2018/#sec-All-Variables-Used + // https://spec.graphql.org/October2021/#sec-All-Variables-Used auto position = variable.second.get().begin(); std::ostringstream error; @@ -918,7 +948,7 @@ bool ValidateExecutableVisitor::validateInputValue( if (itrVariable == _operationVariables->end()) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Uses-Defined + // https://spec.graphql.org/October2021/#sec-All-Variable-Uses-Defined std::ostringstream message; message << "Undefined variable name: " << variable.name; @@ -1046,7 +1076,7 @@ bool ValidateExecutableVisitor::validateInputValue( if (itrField == itrFields->second.end()) { - // http://spec.graphql.org/June2018/#sec-Input-Object-Field-Names + // https://spec.graphql.org/October2021/#sec-Input-Object-Field-Names std::ostringstream message; message << "Undefined Input Object field type: " << name @@ -1097,7 +1127,7 @@ bool ValidateExecutableVisitor::validateInputValue( if (fieldKind == introspection::TypeKind::NON_NULL) { - // http://spec.graphql.org/June2018/#sec-Input-Object-Required-Fields + // https://spec.graphql.org/October2021/#sec-Input-Object-Required-Fields std::ostringstream message; message << "Missing Input Object field type: " << name @@ -1272,7 +1302,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, { if (!isNonNull) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Expected Non-Null variable type", position }); return false; } @@ -1293,7 +1323,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, { if (variableKind != inputKind) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Expected List variable type", position }); return false; } @@ -1322,7 +1352,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, { if (variableKind != inputKind) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Expected Input Object variable type", position }); return false; } @@ -1334,7 +1364,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, { if (variableKind != inputKind) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Expected Enum variable type", position }); return false; } @@ -1346,7 +1376,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, { if (variableKind != inputKind) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Expected Scalar variable type", position }); return false; } @@ -1356,7 +1386,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, default: { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed _errors.push_back({ "Unexpected input type", position }); return false; } @@ -1380,7 +1410,7 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, if (variableName != inputName) { - // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed + // https://spec.graphql.org/October2021/#sec-All-Variable-Usages-are-Allowed std::ostringstream message; message << "Incompatible variable type: " << variableName << " name: " << inputName; @@ -1544,7 +1574,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (itrType == _typeFields.end()) { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + // https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections auto position = field.begin(); std::ostringstream message; @@ -1562,7 +1592,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) case introspection::TypeKind::OBJECT: case introspection::TypeKind::INTERFACE: { - // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types + // https://spec.graphql.org/October2021/#sec-Field-Selections innerType = getFieldType(itrType->second, name); wrappedType = getWrappedFieldType(itrType->second, name); break; @@ -1572,7 +1602,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) { if (name != R"gql(__typename)gql"sv) { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + // https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections auto position = field.begin(); std::ostringstream message; @@ -1583,7 +1613,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) return; } - // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types + // https://spec.graphql.org/October2021/#sec-Field-Selections innerType = getValidateType(_schema->LookupType("String"sv)); wrappedType = getValidateType( _schema->WrapType(introspection::TypeKind::NON_NULL, getSharedType(innerType))); @@ -1596,7 +1626,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (!innerType) { - // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types + // https://spec.graphql.org/October2021/#sec-Field-Selections auto position = field.begin(); std::ostringstream message; @@ -1622,7 +1652,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) std::list argumentNames; peg::on_first_child(field, - [this, &name, &validateArguments, &argumentLocations, &argumentNames]( + [this, name, &validateArguments, &argumentLocations, &argumentNames]( const peg::ast_node& child) { for (auto& argument : child.children) { @@ -1631,7 +1661,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (validateArguments.find(argumentName) != validateArguments.end()) { - // http://spec.graphql.org/June2018/#sec-Argument-Uniqueness + // https://spec.graphql.org/October2021/#sec-Argument-Uniqueness std::ostringstream message; message << "Conflicting argument type: " << _scopedType->get().name() @@ -1668,7 +1698,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } else { - // http://spec.graphql.org/June2018/#sec-Field-Selection-Merging + // https://spec.graphql.org/October2021/#sec-Field-Selection-Merging auto position = field.begin(); std::ostringstream message; @@ -1688,7 +1718,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (itrArgument == itrField->second.arguments.end()) { - // http://spec.graphql.org/June2018/#sec-Argument-Names + // https://spec.graphql.org/October2021/#sec-Argument-Names std::ostringstream message; message << "Undefined argument type: " << _scopedType->get().name() @@ -1710,7 +1740,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) itrArgument->second, argument.second.type)) { - // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type + // https://spec.graphql.org/October2021/#sec-Values-of-Correct-Type std::ostringstream message; message << "Incompatible argument type: " << _scopedType->get().name() @@ -1731,7 +1761,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (argument.second.type && introspection::TypeKind::NON_NULL == argument.second.type->get().kind()) { - // http://spec.graphql.org/June2018/#sec-Required-Arguments + // https://spec.graphql.org/October2021/#sec-Required-Arguments auto position = field.begin(); std::ostringstream message; @@ -1760,8 +1790,10 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) auto outerType = std::move(_scopedType); auto outerFields = std::move(_selectionFields); auto outerFieldCount = _fieldCount; + auto outerIntrospectionFieldCount = _introspectionFieldCount; _fieldCount = 0; + _introspectionFieldCount = 0; _selectionFields.clear(); _scopedType = std::move(innerType); @@ -1771,12 +1803,13 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) _scopedType = std::move(outerType); _selectionFields = std::move(outerFields); subFieldCount = _fieldCount; + _introspectionFieldCount = outerIntrospectionFieldCount; _fieldCount = outerFieldCount; } if (subFieldCount == 0 && !isScalarType(innerType->get().kind())) { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + // https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections auto position = field.begin(); std::ostringstream message; @@ -1787,6 +1820,14 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } ++_fieldCount; + + constexpr auto c_introspectionFieldPrefix = R"gql(__)gql"sv; + + if (name.size() >= c_introspectionFieldPrefix.size() + && name.substr(0, c_introspectionFieldPrefix.size()) == c_introspectionFieldPrefix) + { + ++_introspectionFieldCount; + } } void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread) @@ -1800,7 +1841,7 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen if (itr == _fragmentDefinitions.end()) { - // http://spec.graphql.org/June2018/#sec-Fragment-spread-target-defined + // https://spec.graphql.org/October2021/#sec-Fragment-spread-target-defined auto position = fragmentSpread.begin(); std::ostringstream message; @@ -1814,7 +1855,7 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen { if (_fragmentCycles.emplace(name).second) { - // http://spec.graphql.org/June2018/#sec-Fragment-spreads-must-not-form-cycles + // https://spec.graphql.org/October2021/#sec-Fragment-spreads-must-not-form-cycles auto position = fragmentSpread.begin(); std::ostringstream message; @@ -1833,7 +1874,7 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen if (itrInner == _types.end() || !matchesScopedType(innerType)) { - // http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible + // https://spec.graphql.org/October2021/#sec-Fragment-spread-is-possible auto position = fragmentSpread.begin(); std::ostringstream message; @@ -1885,7 +1926,7 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF if (itrInner == _types.end()) { - // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence + // https://spec.graphql.org/October2021/#sec-Fragment-Spread-Type-Existence std::ostringstream message; message << "Undefined target type on inline fragment name: " << innerType; @@ -1898,8 +1939,8 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF if (isScalarType(fragmentType->get().kind()) || !matchesScopedType(innerType)) { - // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types - // http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible + // https://spec.graphql.org/October2021/#sec-Fragments-On-Composite-Types + // https://spec.graphql.org/October2021/#sec-Fragment-spread-is-possible std::ostringstream message; message << (isScalarType(fragmentType->get().kind()) @@ -1938,27 +1979,27 @@ void ValidateExecutableVisitor::visitDirectives( directiveName = child.string_view(); }); - if (!uniqueDirectives.emplace(directiveName).second) + const auto itrDirective = _directives.find(directiveName); + + if (itrDirective == _directives.end()) { - // http://spec.graphql.org/June2018/#sec-Directives-Are-Unique-Per-Location + // https://spec.graphql.org/October2021/#sec-Directives-Are-Defined auto position = directive->begin(); std::ostringstream message; - message << "Conflicting directive name: " << directiveName; + message << "Undefined directive name: " << directiveName; _errors.push_back({ message.str(), { position.line, position.column } }); continue; } - auto itrDirective = _directives.find(directiveName); - - if (itrDirective == _directives.end()) + if (!itrDirective->second.isRepeatable && !uniqueDirectives.emplace(directiveName).second) { - // http://spec.graphql.org/June2018/#sec-Directives-Are-Defined + // https://spec.graphql.org/October2021/#sec-Directives-Are-Unique-Per-Location auto position = directive->begin(); std::ostringstream message; - message << "Undefined directive name: " << directiveName; + message << "Conflicting directive name: " << directiveName; _errors.push_back({ message.str(), { position.line, position.column } }); continue; @@ -1966,7 +2007,7 @@ void ValidateExecutableVisitor::visitDirectives( if (itrDirective->second.locations.find(location) == itrDirective->second.locations.end()) { - // http://spec.graphql.org/June2018/#sec-Directives-Are-In-Valid-Locations + // https://spec.graphql.org/October2021/#sec-Directives-Are-In-Valid-Locations auto position = directive->begin(); std::ostringstream message; @@ -2023,7 +2064,7 @@ void ValidateExecutableVisitor::visitDirectives( if (validateArguments.find(argumentName) != validateArguments.end()) { - // http://spec.graphql.org/June2018/#sec-Argument-Uniqueness + // https://spec.graphql.org/October2021/#sec-Argument-Uniqueness std::ostringstream message; message << "Conflicting argument directive: " << directiveName @@ -2047,7 +2088,7 @@ void ValidateExecutableVisitor::visitDirectives( if (itrArgument == itrDirective->second.arguments.end()) { - // http://spec.graphql.org/June2018/#sec-Argument-Names + // https://spec.graphql.org/October2021/#sec-Argument-Names std::ostringstream message; message << "Undefined argument directive: " << directiveName @@ -2069,7 +2110,7 @@ void ValidateExecutableVisitor::visitDirectives( itrArgument->second, argument.second.type)) { - // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type + // https://spec.graphql.org/October2021/#sec-Values-of-Correct-Type std::ostringstream message; message << "Incompatible argument directive: " << directiveName @@ -2090,7 +2131,7 @@ void ValidateExecutableVisitor::visitDirectives( if (argument.second.type && introspection::TypeKind::NON_NULL == argument.second.type->get().kind()) { - // http://spec.graphql.org/June2018/#sec-Required-Arguments + // https://spec.graphql.org/October2021/#sec-Required-Arguments auto position = directive->begin(); std::ostringstream message; diff --git a/src/introspection/DirectiveObject.cpp b/src/introspection/DirectiveObject.cpp index 91e42d95..0c58c0a0 100644 --- a/src/introspection/DirectiveObject.cpp +++ b/src/introspection/DirectiveObject.cpp @@ -45,7 +45,8 @@ service::ResolverMap Directive::getResolvers() const noexcept { R"gql(name)gql"sv, [this](service::ResolverParams&& params) { return resolveName(std::move(params)); } }, { R"gql(locations)gql"sv, [this](service::ResolverParams&& params) { return resolveLocations(std::move(params)); } }, { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, - { R"gql(description)gql"sv, [this](service::ResolverParams&& params) { return resolveDescription(std::move(params)); } } + { R"gql(description)gql"sv, [this](service::ResolverParams&& params) { return resolveDescription(std::move(params)); } }, + { R"gql(isRepeatable)gql"sv, [this](service::ResolverParams&& params) { return resolveIsRepeatable(std::move(params)); } } }; } @@ -85,6 +86,15 @@ service::AwaitableResolver Directive::resolveArgs(service::ResolverParams&& para return service::ModifiedResult::convert(std::move(result), std::move(params)); } +service::AwaitableResolver Directive::resolveIsRepeatable(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + auto result = _pimpl->getIsRepeatable(); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + service::AwaitableResolver Directive::resolve_typename(service::ResolverParams&& params) const { return service::ModifiedResult::convert(std::string{ R"gql(__Directive)gql" }, std::move(params)); @@ -98,7 +108,8 @@ void AddDirectiveDetails(const std::shared_ptr& typeDirectiv schema::Field::Make(R"gql(name)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv))), schema::Field::Make(R"gql(description)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv)), schema::Field::Make(R"gql(locations)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->WrapType(introspection::TypeKind::LIST, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__DirectiveLocation)gql"sv))))), - schema::Field::Make(R"gql(args)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->WrapType(introspection::TypeKind::LIST, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__InputValue)gql"sv))))) + schema::Field::Make(R"gql(args)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->WrapType(introspection::TypeKind::LIST, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__InputValue)gql"sv))))), + schema::Field::Make(R"gql(isRepeatable)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Boolean)gql"sv))) }); } diff --git a/src/introspection/DirectiveObject.h b/src/introspection/DirectiveObject.h index e0953288..2349fe17 100644 --- a/src/introspection/DirectiveObject.h +++ b/src/introspection/DirectiveObject.h @@ -20,6 +20,7 @@ class Directive service::AwaitableResolver resolveDescription(service::ResolverParams&& params) const; service::AwaitableResolver resolveLocations(service::ResolverParams&& params) const; service::AwaitableResolver resolveArgs(service::ResolverParams&& params) const; + service::AwaitableResolver resolveIsRepeatable(service::ResolverParams&& params) const; service::AwaitableResolver resolve_typename(service::ResolverParams&& params) const; @@ -31,6 +32,7 @@ class Directive virtual service::FieldResult> getDescription() const = 0; virtual service::FieldResult> getLocations() const = 0; virtual service::FieldResult>> getArgs() const = 0; + virtual service::FieldResult getIsRepeatable() const = 0; }; template @@ -62,6 +64,11 @@ class Directive return { _pimpl->getArgs() }; } + service::FieldResult getIsRepeatable() const final + { + return { _pimpl->getIsRepeatable() }; + } + private: const std::shared_ptr _pimpl; }; diff --git a/src/introspection/IntrospectionSchema.cpp b/src/introspection/IntrospectionSchema.cpp index bf7cd362..0bf9ba6b 100644 --- a/src/introspection/IntrospectionSchema.cpp +++ b/src/introspection/IntrospectionSchema.cpp @@ -3,7 +3,6 @@ // WARNING! Do not edit this file manually, your changes will be overwritten. - #include "graphqlservice/internal/Introspection.h" #include @@ -63,7 +62,7 @@ service::AwaitableResolver ModifiedResult::convert(serv }); } -static const std::array s_namesDirectiveLocation = { +static const std::array s_namesDirectiveLocation = { R"gql(QUERY)gql"sv, R"gql(MUTATION)gql"sv, R"gql(SUBSCRIPTION)gql"sv, @@ -71,6 +70,7 @@ static const std::array s_namesDirectiveLocation = { R"gql(FRAGMENT_DEFINITION)gql"sv, R"gql(FRAGMENT_SPREAD)gql"sv, R"gql(INLINE_FRAGMENT)gql"sv, + R"gql(VARIABLE_DEFINITION)gql"sv, R"gql(SCHEMA)gql"sv, R"gql(SCALAR)gql"sv, R"gql(OBJECT)gql"sv, @@ -122,26 +122,26 @@ namespace introspection { void AddTypesToSchema(const std::shared_ptr& schema) { - schema->AddType(R"gql(Boolean)gql"sv, schema::ScalarType::Make(R"gql(Boolean)gql"sv, R"md(Built-in type)md")); - schema->AddType(R"gql(Float)gql"sv, schema::ScalarType::Make(R"gql(Float)gql"sv, R"md(Built-in type)md")); - schema->AddType(R"gql(ID)gql"sv, schema::ScalarType::Make(R"gql(ID)gql"sv, R"md(Built-in type)md")); - schema->AddType(R"gql(Int)gql"sv, schema::ScalarType::Make(R"gql(Int)gql"sv, R"md(Built-in type)md")); - schema->AddType(R"gql(String)gql"sv, schema::ScalarType::Make(R"gql(String)gql"sv, R"md(Built-in type)md")); + schema->AddType(R"gql(Boolean)gql"sv, schema::ScalarType::Make(R"gql(Boolean)gql"sv, R"md(Built-in type)md"sv, R"url(https://spec.graphql.org/October2021/#sec-Boolean)url"sv)); + schema->AddType(R"gql(Float)gql"sv, schema::ScalarType::Make(R"gql(Float)gql"sv, R"md(Built-in type)md"sv, R"url(https://spec.graphql.org/October2021/#sec-Float)url"sv)); + schema->AddType(R"gql(ID)gql"sv, schema::ScalarType::Make(R"gql(ID)gql"sv, R"md(Built-in type)md"sv, R"url(https://spec.graphql.org/October2021/#sec-ID)url"sv)); + schema->AddType(R"gql(Int)gql"sv, schema::ScalarType::Make(R"gql(Int)gql"sv, R"md(Built-in type)md"sv, R"url(https://spec.graphql.org/October2021/#sec-Int)url"sv)); + schema->AddType(R"gql(String)gql"sv, schema::ScalarType::Make(R"gql(String)gql"sv, R"md(Built-in type)md"sv, R"url(https://spec.graphql.org/October2021/#sec-String)url"sv)); auto typeTypeKind = schema::EnumType::Make(R"gql(__TypeKind)gql"sv, R"md()md"sv); schema->AddType(R"gql(__TypeKind)gql"sv, typeTypeKind); auto typeDirectiveLocation = schema::EnumType::Make(R"gql(__DirectiveLocation)gql"sv, R"md()md"sv); schema->AddType(R"gql(__DirectiveLocation)gql"sv, typeDirectiveLocation); - auto typeSchema = schema::ObjectType::Make(R"gql(__Schema)gql"sv, R"md()md"); + auto typeSchema = schema::ObjectType::Make(R"gql(__Schema)gql"sv, R"md()md"sv); schema->AddType(R"gql(__Schema)gql"sv, typeSchema); - auto typeType = schema::ObjectType::Make(R"gql(__Type)gql"sv, R"md()md"); + auto typeType = schema::ObjectType::Make(R"gql(__Type)gql"sv, R"md()md"sv); schema->AddType(R"gql(__Type)gql"sv, typeType); - auto typeField = schema::ObjectType::Make(R"gql(__Field)gql"sv, R"md()md"); + auto typeField = schema::ObjectType::Make(R"gql(__Field)gql"sv, R"md()md"sv); schema->AddType(R"gql(__Field)gql"sv, typeField); - auto typeInputValue = schema::ObjectType::Make(R"gql(__InputValue)gql"sv, R"md()md"); + auto typeInputValue = schema::ObjectType::Make(R"gql(__InputValue)gql"sv, R"md()md"sv); schema->AddType(R"gql(__InputValue)gql"sv, typeInputValue); - auto typeEnumValue = schema::ObjectType::Make(R"gql(__EnumValue)gql"sv, R"md()md"); + auto typeEnumValue = schema::ObjectType::Make(R"gql(__EnumValue)gql"sv, R"md()md"sv); schema->AddType(R"gql(__EnumValue)gql"sv, typeEnumValue); - auto typeDirective = schema::ObjectType::Make(R"gql(__Directive)gql"sv, R"md()md"); + auto typeDirective = schema::ObjectType::Make(R"gql(__Directive)gql"sv, R"md()md"sv); schema->AddType(R"gql(__Directive)gql"sv, typeDirective); typeTypeKind->AddEnumValues({ @@ -162,6 +162,7 @@ void AddTypesToSchema(const std::shared_ptr& schema) { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::FRAGMENT_DEFINITION)], R"md()md"sv, std::nullopt }, { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::FRAGMENT_SPREAD)], R"md()md"sv, std::nullopt }, { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::INLINE_FRAGMENT)], R"md()md"sv, std::nullopt }, + { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::VARIABLE_DEFINITION)], R"md()md"sv, std::nullopt }, { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::SCHEMA)], R"md()md"sv, std::nullopt }, { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::SCALAR)], R"md()md"sv, std::nullopt }, { service::s_namesDirectiveLocation[static_cast(introspection::DirectiveLocation::OBJECT)], R"md()md"sv, std::nullopt }, @@ -188,20 +189,25 @@ void AddTypesToSchema(const std::shared_ptr& schema) introspection::DirectiveLocation::INLINE_FRAGMENT }, { schema::InputValue::Make(R"gql(if)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Boolean)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(include)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { schema::InputValue::Make(R"gql(if)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Boolean)gql"sv)), R"gql()gql"sv) - })); + }, false)); schema->AddDirective(schema::Directive::Make(R"gql(deprecated)gql"sv, R"md()md"sv, { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { schema::InputValue::Make(R"gql(reason)gql"sv, R"md()md"sv, schema->LookupType(R"gql(String)gql"sv), R"gql("No longer supported")gql"sv) - })); + }, false)); + schema->AddDirective(schema::Directive::Make(R"gql(specifiedBy)gql"sv, R"md()md"sv, { + introspection::DirectiveLocation::SCALAR + }, { + schema::InputValue::Make(R"gql(url)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv) + }, false)); } } // namespace introspection diff --git a/src/introspection/IntrospectionSchema.h b/src/introspection/IntrospectionSchema.h index a701780a..d833ce84 100644 --- a/src/introspection/IntrospectionSchema.h +++ b/src/introspection/IntrospectionSchema.h @@ -54,6 +54,7 @@ enum class DirectiveLocation FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, + VARIABLE_DEFINITION, SCHEMA, SCALAR, OBJECT, diff --git a/src/introspection/SchemaObject.cpp b/src/introspection/SchemaObject.cpp index 16c857a6..d0face1e 100644 --- a/src/introspection/SchemaObject.cpp +++ b/src/introspection/SchemaObject.cpp @@ -46,11 +46,21 @@ service::ResolverMap Schema::getResolvers() const noexcept { R"gql(queryType)gql"sv, [this](service::ResolverParams&& params) { return resolveQueryType(std::move(params)); } }, { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, { R"gql(directives)gql"sv, [this](service::ResolverParams&& params) { return resolveDirectives(std::move(params)); } }, + { R"gql(description)gql"sv, [this](service::ResolverParams&& params) { return resolveDescription(std::move(params)); } }, { R"gql(mutationType)gql"sv, [this](service::ResolverParams&& params) { return resolveMutationType(std::move(params)); } }, { R"gql(subscriptionType)gql"sv, [this](service::ResolverParams&& params) { return resolveSubscriptionType(std::move(params)); } } }; } +service::AwaitableResolver Schema::resolveDescription(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + auto result = _pimpl->getDescription(); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + service::AwaitableResolver Schema::resolveTypes(service::ResolverParams&& params) const { std::unique_lock resolverLock(_resolverMutex); @@ -106,6 +116,7 @@ service::AwaitableResolver Schema::resolve_typename(service::ResolverParams&& pa void AddSchemaDetails(const std::shared_ptr& typeSchema, const std::shared_ptr& schema) { typeSchema->AddFields({ + schema::Field::Make(R"gql(description)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv)), schema::Field::Make(R"gql(types)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->WrapType(introspection::TypeKind::LIST, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__Type)gql"sv))))), schema::Field::Make(R"gql(queryType)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__Type)gql"sv))), schema::Field::Make(R"gql(mutationType)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(__Type)gql"sv)), diff --git a/src/introspection/SchemaObject.h b/src/introspection/SchemaObject.h index f81ab9c3..9a4b082a 100644 --- a/src/introspection/SchemaObject.h +++ b/src/introspection/SchemaObject.h @@ -16,6 +16,7 @@ class Schema : public service::Object { private: + service::AwaitableResolver resolveDescription(service::ResolverParams&& params) const; service::AwaitableResolver resolveTypes(service::ResolverParams&& params) const; service::AwaitableResolver resolveQueryType(service::ResolverParams&& params) const; service::AwaitableResolver resolveMutationType(service::ResolverParams&& params) const; @@ -28,6 +29,7 @@ class Schema { virtual ~Concept() = default; + virtual service::FieldResult> getDescription() const = 0; virtual service::FieldResult>> getTypes() const = 0; virtual service::FieldResult> getQueryType() const = 0; virtual service::FieldResult> getMutationType() const = 0; @@ -44,6 +46,11 @@ class Schema { } + service::FieldResult> getDescription() const final + { + return { _pimpl->getDescription() }; + } + service::FieldResult>> getTypes() const final { return { _pimpl->getTypes() }; diff --git a/src/introspection/TypeObject.cpp b/src/introspection/TypeObject.cpp index aecb5d2f..08c3e625 100644 --- a/src/introspection/TypeObject.cpp +++ b/src/introspection/TypeObject.cpp @@ -53,7 +53,8 @@ service::ResolverMap Type::getResolvers() const noexcept { R"gql(interfaces)gql"sv, [this](service::ResolverParams&& params) { return resolveInterfaces(std::move(params)); } }, { R"gql(description)gql"sv, [this](service::ResolverParams&& params) { return resolveDescription(std::move(params)); } }, { R"gql(inputFields)gql"sv, [this](service::ResolverParams&& params) { return resolveInputFields(std::move(params)); } }, - { R"gql(possibleTypes)gql"sv, [this](service::ResolverParams&& params) { return resolvePossibleTypes(std::move(params)); } } + { R"gql(possibleTypes)gql"sv, [this](service::ResolverParams&& params) { return resolvePossibleTypes(std::move(params)); } }, + { R"gql(specifiedByURL)gql"sv, [this](service::ResolverParams&& params) { return resolveSpecifiedByURL(std::move(params)); } } }; } @@ -168,6 +169,15 @@ service::AwaitableResolver Type::resolveOfType(service::ResolverParams&& params) return service::ModifiedResult::convert(std::move(result), std::move(params)); } +service::AwaitableResolver Type::resolveSpecifiedByURL(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + auto result = _pimpl->getSpecifiedByURL(); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + service::AwaitableResolver Type::resolve_typename(service::ResolverParams&& params) const { return service::ModifiedResult::convert(std::string{ R"gql(__Type)gql" }, std::move(params)); @@ -190,7 +200,8 @@ void AddTypeDetails(const std::shared_ptr& typeType, const s schema::InputValue::Make(R"gql(includeDeprecated)gql"sv, R"md()md"sv, schema->LookupType(R"gql(Boolean)gql"sv), R"gql(false)gql"sv) }), schema::Field::Make(R"gql(inputFields)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::LIST, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(__InputValue)gql"sv)))), - schema::Field::Make(R"gql(ofType)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(__Type)gql"sv)) + schema::Field::Make(R"gql(ofType)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(__Type)gql"sv)), + schema::Field::Make(R"gql(specifiedByURL)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv)) }); } diff --git a/src/introspection/TypeObject.h b/src/introspection/TypeObject.h index 87c2101e..484980e1 100644 --- a/src/introspection/TypeObject.h +++ b/src/introspection/TypeObject.h @@ -25,6 +25,7 @@ class Type service::AwaitableResolver resolveEnumValues(service::ResolverParams&& params) const; service::AwaitableResolver resolveInputFields(service::ResolverParams&& params) const; service::AwaitableResolver resolveOfType(service::ResolverParams&& params) const; + service::AwaitableResolver resolveSpecifiedByURL(service::ResolverParams&& params) const; service::AwaitableResolver resolve_typename(service::ResolverParams&& params) const; @@ -41,6 +42,7 @@ class Type virtual service::FieldResult>>> getEnumValues(std::optional&& includeDeprecatedArg) const = 0; virtual service::FieldResult>>> getInputFields() const = 0; virtual service::FieldResult> getOfType() const = 0; + virtual service::FieldResult> getSpecifiedByURL() const = 0; }; template @@ -97,6 +99,11 @@ class Type return { _pimpl->getOfType() }; } + service::FieldResult> getSpecifiedByURL() const final + { + return { _pimpl->getSpecifiedByURL() }; + } + private: const std::shared_ptr _pimpl; }; diff --git a/src/introspection/schema.introspection.graphql b/src/introspection/schema.introspection.graphql index e5098ac2..5145f3b2 100644 --- a/src/introspection/schema.introspection.graphql +++ b/src/introspection/schema.introspection.graphql @@ -1,8 +1,9 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -# Introspection Schema: http://spec.graphql.org/June2018/#sec-Schema-Introspection +# Introspection Schema: https://spec.graphql.org/October2021/#sec-Schema-Introspection.Schema-Introspection-Schema type __Schema { + description: String types: [__Type!]! queryType: __Type! mutationType: __Type @@ -14,24 +15,31 @@ type __Type { kind: __TypeKind! name: String description: String - - # OBJECT and INTERFACE only + # must be non-null for OBJECT and INTERFACE, otherwise null. fields(includeDeprecated: Boolean = false): [__Field!] - - # OBJECT only + # must be non-null for OBJECT and INTERFACE, otherwise null. interfaces: [__Type!] - - # INTERFACE and UNION only + # must be non-null for INTERFACE and UNION, otherwise null. possibleTypes: [__Type!] - - # ENUM only + # must be non-null for ENUM, otherwise null. enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - - # INPUT_OBJECT only + # must be non-null for INPUT_OBJECT, otherwise null. inputFields: [__InputValue!] - - # NON_NULL and LIST only + # must be non-null for NON_NULL and LIST, otherwise null. ofType: __Type + # may be non-null for custom SCALAR, otherwise null. + specifiedByURL: String +} + +enum __TypeKind { + SCALAR + OBJECT + INTERFACE + UNION + ENUM + INPUT_OBJECT + LIST + NON_NULL } type __Field { @@ -57,22 +65,12 @@ type __EnumValue { deprecationReason: String } -enum __TypeKind { - SCALAR - OBJECT - INTERFACE - UNION - ENUM - INPUT_OBJECT - LIST - NON_NULL -} - type __Directive { name: String! description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! + isRepeatable: Boolean! } enum __DirectiveLocation { @@ -83,6 +81,7 @@ enum __DirectiveLocation { FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT + VARIABLE_DEFINITION SCHEMA SCALAR OBJECT @@ -102,3 +101,4 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @deprecated( reason: String = "No longer supported" ) on FIELD_DEFINITION | ENUM_VALUE +directive @specifiedBy(url: String!) on SCALAR diff --git a/test/TodayTests.cpp b/test/TodayTests.cpp index b47633ea..da8273c6 100644 --- a/test/TodayTests.cpp +++ b/test/TodayTests.cpp @@ -955,7 +955,7 @@ TEST_F(TodayServiceCase, NestedFragmentDirectives) inlineFragmentNested: nested @fieldTag(field: "nested3") { ...on NestedType @inlineFragmentTag(inlineFragment: "inlineFragment4") { ...on NestedType @inlineFragmentTag(inlineFragment: "inlineFragment5") { - inlineFragmentNested: nested @fieldTag(field: "nested4") { + inlineFragmentNested: nested @repeatableOnField @fieldTag(field: "nested4") @repeatableOnField { depth @fieldTag(field: "depth4") } } @@ -995,61 +995,107 @@ TEST_F(TodayServiceCase, NestedFragmentDirectives) capturedParams.pop(); const auto params1 = std::move(capturedParams.top()); capturedParams.pop(); - const auto queryTag1 = - service::ScalarArgument::require("queryTag", params1.operationDirectives); + ASSERT_EQ(size_t(1), params1.operationDirectives.size()) << "missing operation directive"; + const auto itrQueryTag1 = params1.operationDirectives.cbegin(); + ASSERT_TRUE(itrQueryTag1->first == "queryTag"sv) << "missing required directive"; + const auto& queryTag1 = itrQueryTag1->second; const auto query1 = service::StringArgument::require("query", queryTag1); const auto fragmentDefinitionCount1 = params1.fragmentDefinitionDirectives.size(); const auto fragmentSpreadCount1 = params1.fragmentSpreadDirectives.size(); const auto inlineFragmentCount1 = params1.inlineFragmentDirectives.size(); - const auto fieldTag1 = - service::ScalarArgument::require("fieldTag", params1.fieldDirectives); + ASSERT_EQ(size_t(1), params1.fieldDirectives.size()) << "missing operation directive"; + const auto itrFieldTag1 = params1.fieldDirectives.cbegin(); + ASSERT_TRUE(itrFieldTag1->first == "fieldTag"sv) << "missing required directive"; + const auto& fieldTag1 = itrFieldTag1->second; const auto field1 = service::StringArgument::require("field", fieldTag1); - const auto queryTag2 = - service::ScalarArgument::require("queryTag", params2.operationDirectives); + ASSERT_EQ(size_t(1), params2.operationDirectives.size()) << "missing operation directive"; + const auto itrQueryTag2 = params2.operationDirectives.cbegin(); + ASSERT_TRUE(itrQueryTag2->first == "queryTag"sv) << "missing required directive"; + const auto& queryTag2 = itrQueryTag2->second; const auto query2 = service::StringArgument::require("query", queryTag2); - const auto fragmentDefinitionTag2 = - service::ScalarArgument::require("fragmentDefinitionTag", - params2.fragmentDefinitionDirectives); + ASSERT_EQ(size_t(1), params2.fragmentDefinitionDirectives.size()) + << "missing fragment definition directive"; + const auto itrFragmentDefinitionTag2 = params2.fragmentDefinitionDirectives.cbegin(); + ASSERT_TRUE(itrFragmentDefinitionTag2->first == "fragmentDefinitionTag"sv) + << "missing fragment definition directive"; + const auto& fragmentDefinitionTag2 = itrFragmentDefinitionTag2->second; const auto fragmentDefinition2 = service::StringArgument::require("fragmentDefinition", fragmentDefinitionTag2); - const auto fragmentSpreadTag2 = - service::ScalarArgument::require("fragmentSpreadTag", params2.fragmentSpreadDirectives); + ASSERT_EQ(size_t(1), params2.fragmentSpreadDirectives.size()) + << "missing fragment spread directive"; + const auto itrFragmentSpreadTag2 = params2.fragmentSpreadDirectives.cbegin(); + ASSERT_TRUE(itrFragmentSpreadTag2->first == "fragmentSpreadTag"sv) + << "missing fragment spread directive"; + const auto& fragmentSpreadTag2 = itrFragmentSpreadTag2->second; const auto fragmentSpread2 = service::StringArgument::require("fragmentSpread", fragmentSpreadTag2); const auto inlineFragmentCount2 = params2.inlineFragmentDirectives.size(); - const auto fieldTag2 = - service::ScalarArgument::require("fieldTag", params2.fieldDirectives); + ASSERT_EQ(size_t(1), params2.fieldDirectives.size()) << "missing field directive"; + const auto itrFieldTag2 = params2.fieldDirectives.cbegin(); + ASSERT_TRUE(itrFieldTag2->first == "fieldTag"sv) << "missing field directive"; + const auto& fieldTag2 = itrFieldTag2->second; const auto field2 = service::StringArgument::require("field", fieldTag2); - const auto queryTag3 = - service::ScalarArgument::require("queryTag", params3.operationDirectives); + ASSERT_EQ(size_t(1), params3.operationDirectives.size()) << "missing operation directive"; + const auto itrQueryTag3 = params3.operationDirectives.cbegin(); + ASSERT_TRUE(itrQueryTag3->first == "queryTag"sv) << "missing required directive"; + const auto& queryTag3 = itrQueryTag3->second; const auto query3 = service::StringArgument::require("query", queryTag3); - const auto fragmentDefinitionTag3 = - service::ScalarArgument::require("fragmentDefinitionTag", - params3.fragmentDefinitionDirectives); + ASSERT_EQ(size_t(1), params3.fragmentDefinitionDirectives.size()) + << "missing fragment definition directive"; + const auto itrFragmentDefinitionTag3 = params3.fragmentDefinitionDirectives.cbegin(); + ASSERT_TRUE(itrFragmentDefinitionTag3->first == "fragmentDefinitionTag"sv) + << "missing fragment definition directive"; + const auto& fragmentDefinitionTag3 = itrFragmentDefinitionTag3->second; const auto fragmentDefinition3 = service::StringArgument::require("fragmentDefinition", fragmentDefinitionTag3); - const auto fragmentSpreadTag3 = - service::ScalarArgument::require("fragmentSpreadTag", params3.fragmentSpreadDirectives); + ASSERT_EQ(size_t(1), params3.fragmentSpreadDirectives.size()) + << "missing fragment spread directive"; + const auto itrFragmentSpreadTag3 = params3.fragmentSpreadDirectives.cbegin(); + ASSERT_TRUE(itrFragmentSpreadTag3->first == "fragmentSpreadTag"sv) + << "missing fragment spread directive"; + const auto& fragmentSpreadTag3 = itrFragmentSpreadTag3->second; const auto fragmentSpread3 = service::StringArgument::require("fragmentSpread", fragmentSpreadTag3); - const auto inlineFragmentTag3 = - service::ScalarArgument::require("inlineFragmentTag", params3.inlineFragmentDirectives); + ASSERT_EQ(size_t(1), params3.inlineFragmentDirectives.size()) + << "missing inline fragment directive"; + const auto itrInlineFragmentTag3 = params3.inlineFragmentDirectives.cbegin(); + ASSERT_TRUE(itrInlineFragmentTag3->first == "inlineFragmentTag"sv); + const auto& inlineFragmentTag3 = itrInlineFragmentTag3->second; const auto inlineFragment3 = service::StringArgument::require("inlineFragment", inlineFragmentTag3); - const auto fieldTag3 = - service::ScalarArgument::require("fieldTag", params3.fieldDirectives); + ASSERT_EQ(size_t(1), params3.fieldDirectives.size()) << "missing field directive"; + const auto itrFieldTag3 = params3.fieldDirectives.cbegin(); + ASSERT_TRUE(itrFieldTag3->first == "fieldTag"sv) << "missing field directive"; + const auto& fieldTag3 = itrFieldTag3->second; const auto field3 = service::StringArgument::require("field", fieldTag3); - const auto queryTag4 = - service::ScalarArgument::require("queryTag", params4.operationDirectives); + ASSERT_EQ(size_t(1), params4.operationDirectives.size()) << "missing operation directive"; + const auto itrQueryTag4 = params4.operationDirectives.cbegin(); + ASSERT_TRUE(itrQueryTag4->first == "queryTag"sv) << "missing required directive"; + const auto& queryTag4 = itrQueryTag4->second; const auto query4 = service::StringArgument::require("query", queryTag4); const auto fragmentDefinitionCount4 = params4.fragmentDefinitionDirectives.size(); const auto fragmentSpreadCount4 = params4.fragmentSpreadDirectives.size(); - const auto inlineFragmentTag4 = - service::ScalarArgument::require("inlineFragmentTag", params4.inlineFragmentDirectives); + ASSERT_EQ(size_t(1), params4.inlineFragmentDirectives.size()) + << "missing inline fragment directive"; + const auto itrInlineFragmentTag4 = params4.inlineFragmentDirectives.cbegin(); + ASSERT_TRUE(itrInlineFragmentTag4->first == "inlineFragmentTag"sv); + const auto& inlineFragmentTag4 = itrInlineFragmentTag4->second; const auto inlineFragment4 = service::StringArgument::require("inlineFragment", inlineFragmentTag4); - const auto fieldTag4 = - service::ScalarArgument::require("fieldTag", params4.fieldDirectives); + ASSERT_EQ(size_t(3), params4.fieldDirectives.size()) << "missing field directive"; + const auto itrRepeatable1 = params4.fieldDirectives.cbegin(); + ASSERT_TRUE(itrRepeatable1->first == "repeatableOnField"sv) << "missing field directive"; + EXPECT_TRUE(response::Type::Map == itrRepeatable1->second.type()) + << "unexpected arguments type directive"; + EXPECT_EQ(size_t(0), itrRepeatable1->second.size()) << "extra arguments on directive"; + const auto itrFieldTag4 = itrRepeatable1 + 1; + ASSERT_TRUE(itrFieldTag4->first == "fieldTag"sv) << "missing field directive"; + const auto& fieldTag4 = itrFieldTag4->second; + const auto itrRepeatable2 = itrFieldTag4 + 1; + ASSERT_TRUE(itrRepeatable2->first == "repeatableOnField"sv) << "missing field directive"; + EXPECT_TRUE(response::Type::Map == itrRepeatable2->second.type()) + << "unexpected arguments type directive"; + EXPECT_EQ(size_t(0), itrRepeatable2->second.size()) << "extra arguments on directive"; const auto field4 = service::StringArgument::require("field", fieldTag4); ASSERT_EQ(1, depth1); @@ -1079,9 +1125,11 @@ TEST_F(TodayServiceCase, NestedFragmentDirectives) ASSERT_EQ("nested3", field3) << "remember the field directives"; ASSERT_EQ("nested", query4) << "remember the operation directives"; ASSERT_EQ(size_t(0), fragmentDefinitionCount4) - << "traversing a field to a nested object SelectionSet resets the fragment directives"; + << "traversing a field to a nested object SelectionSet resets the fragment " + "directives"; ASSERT_EQ(size_t(0), fragmentSpreadCount4) - << "traversing a field to a nested object SelectionSet resets the fragment directives"; + << "traversing a field to a nested object SelectionSet resets the fragment " + "directives"; ASSERT_EQ("inlineFragment5", inlineFragment4) << "nested inline fragments don't reset, but do overwrite on collision"; ASSERT_EQ("nested4", field4) << "remember the field directives"; diff --git a/test/ValidationTests.cpp b/test/ValidationTests.cpp index 071e9317..e14aa1fa 100644 --- a/test/ValidationTests.cpp +++ b/test/ValidationTests.cpp @@ -34,9 +34,9 @@ class ValidationExamplesCase : public ::testing::Test std::shared_ptr ValidationExamplesCase::_service; -TEST_F(ValidationExamplesCase, CounterExample91) +TEST_F(ValidationExamplesCase, CounterExample102) { - // http://spec.graphql.org/June2018/#example-12752 + // https://spec.graphql.org/October2021/#example-12752 auto query = R"(query getDogName { dog { name @@ -61,9 +61,9 @@ TEST_F(ValidationExamplesCase, CounterExample91) << "error should match"; } -TEST_F(ValidationExamplesCase, Example92) +TEST_F(ValidationExamplesCase, Example103) { - // http://spec.graphql.org/June2018/#example-069e1 + // https://spec.graphql.org/October2021/#example-069e1 auto query = R"(query getDogName { dog { name @@ -83,9 +83,9 @@ TEST_F(ValidationExamplesCase, Example92) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample93) +TEST_F(ValidationExamplesCase, CounterExample104) { - // http://spec.graphql.org/June2018/#example-5e409 + // https://spec.graphql.org/October2021/#example-5e409 auto query = R"(query getName { dog { name @@ -110,9 +110,9 @@ TEST_F(ValidationExamplesCase, CounterExample93) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample94) +TEST_F(ValidationExamplesCase, CounterExample105) { - // http://spec.graphql.org/June2018/#example-77c2e + // https://spec.graphql.org/October2021/#example-77c2e auto query = R"(query dogOperation { dog { name @@ -135,9 +135,9 @@ TEST_F(ValidationExamplesCase, CounterExample94) << "error should match"; } -TEST_F(ValidationExamplesCase, Example95) +TEST_F(ValidationExamplesCase, Example106) { - // http://spec.graphql.org/June2018/#example-be853 + // https://spec.graphql.org/October2021/#example-be853 auto query = R"({ dog { name @@ -149,9 +149,9 @@ TEST_F(ValidationExamplesCase, Example95) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample96) +TEST_F(ValidationExamplesCase, CounterExample107) { - // http://spec.graphql.org/June2018/#example-44b85 + // https://spec.graphql.org/October2021/#example-44b85 auto query = R"({ dog { name @@ -176,9 +176,9 @@ TEST_F(ValidationExamplesCase, CounterExample96) << "error should match"; } -TEST_F(ValidationExamplesCase, Example97) +TEST_F(ValidationExamplesCase, Example108) { - // http://spec.graphql.org/June2018/#example-5bbc3 + // https://spec.graphql.org/October2021/#example-5bbc3 auto query = R"(subscription sub { newMessage { body @@ -191,9 +191,9 @@ TEST_F(ValidationExamplesCase, Example97) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example98) +TEST_F(ValidationExamplesCase, Example109) { - // http://spec.graphql.org/June2018/#example-13061 + // https://spec.graphql.org/October2021/#example-13061 auto query = R"(subscription sub { ...newMessageFields } @@ -210,9 +210,9 @@ TEST_F(ValidationExamplesCase, Example98) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample99) +TEST_F(ValidationExamplesCase, CounterExample110) { - // http://spec.graphql.org/June2018/#example-3997d + // https://spec.graphql.org/October2021/#example-3997d auto query = R"(subscription sub { newMessage { body @@ -231,9 +231,9 @@ TEST_F(ValidationExamplesCase, CounterExample99) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample100) +TEST_F(ValidationExamplesCase, CounterExample111) { - // http://spec.graphql.org/June2018/#example-18466 + // https://spec.graphql.org/October2021/#example-18466 auto query = R"(subscription sub { ...multipleSubscriptions } @@ -256,14 +256,10 @@ TEST_F(ValidationExamplesCase, CounterExample100) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample101) +TEST_F(ValidationExamplesCase, CounterExample112) { - // http://spec.graphql.org/June2018/#example-2353b + // https://spec.graphql.org/October2021/#example-a8fa1 auto query = R"(subscription sub { - newMessage { - body - sender - } __typename })"_graphql; @@ -272,14 +268,14 @@ TEST_F(ValidationExamplesCase, CounterExample101) ASSERT_EQ(errors.size(), 1); EXPECT_EQ( - R"js({"message":"Subscription with more than one root field name: sub","locations":[{"line":1,"column":1}]})js", + R"js({"message":"Subscription with Introspection root field name: sub","locations":[{"line":1,"column":1}]})js", response::toJSON(std::move(errors[0]))) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample102) +TEST_F(ValidationExamplesCase, CounterExample113) { - // http://spec.graphql.org/June2018/#example-48706 + // https://spec.graphql.org/October2021/#example-48706 auto query = R"(fragment fieldNotDefined on Dog { meowVolume } @@ -303,9 +299,9 @@ TEST_F(ValidationExamplesCase, CounterExample102) << "error should match"; } -TEST_F(ValidationExamplesCase, Example103) +TEST_F(ValidationExamplesCase, Example114) { - // http://spec.graphql.org/June2018/#example-d34e0 + // https://spec.graphql.org/October2021/#example-d34e0 auto query = R"(fragment interfaceFieldSelection on Pet { name } @@ -321,9 +317,9 @@ TEST_F(ValidationExamplesCase, Example103) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample104) +TEST_F(ValidationExamplesCase, CounterExample115) { - // http://spec.graphql.org/June2018/#example-db33b + // https://spec.graphql.org/October2021/#example-db33b auto query = R"(fragment definedOnImplementorsButNotInterface on Pet { nickname })"_graphql; @@ -339,9 +335,9 @@ TEST_F(ValidationExamplesCase, CounterExample104) << "error should match"; } -TEST_F(ValidationExamplesCase, Example105) +TEST_F(ValidationExamplesCase, Example116) { - // http://spec.graphql.org/June2018/#example-245fa + // https://spec.graphql.org/October2021/#example-245fa auto query = R"(fragment inDirectFieldSelectionOnUnion on CatOrDog { __typename ... on Pet { @@ -363,9 +359,9 @@ TEST_F(ValidationExamplesCase, Example105) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample106) +TEST_F(ValidationExamplesCase, CounterExample117) { - // http://spec.graphql.org/June2018/#example-252ad + // https://spec.graphql.org/October2021/#example-252ad auto query = R"(fragment directFieldSelectionOnUnion on CatOrDog { name barkVolume @@ -386,9 +382,9 @@ TEST_F(ValidationExamplesCase, CounterExample106) << "error should match"; } -TEST_F(ValidationExamplesCase, Example107) +TEST_F(ValidationExamplesCase, Example118) { - // http://spec.graphql.org/June2018/#example-4e10c + // https://spec.graphql.org/October2021/#example-4e10c auto query = R"(fragment mergeIdenticalFields on Dog { name name @@ -411,9 +407,9 @@ TEST_F(ValidationExamplesCase, Example107) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample108) +TEST_F(ValidationExamplesCase, CounterExample119) { - // http://spec.graphql.org/June2018/#example-a2230 + // https://spec.graphql.org/October2021/#example-a2230 auto query = R"(fragment conflictingBecauseAlias on Dog { name: nickname name @@ -430,9 +426,9 @@ TEST_F(ValidationExamplesCase, CounterExample108) << "error should match"; } -TEST_F(ValidationExamplesCase, Example109) +TEST_F(ValidationExamplesCase, Example120) { - // http://spec.graphql.org/June2018/#example-b6369 + // https://spec.graphql.org/October2021/#example-b6369 auto query = R"(fragment mergeIdenticalFieldsWithIdenticalArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: SIT) @@ -460,9 +456,9 @@ TEST_F(ValidationExamplesCase, Example109) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample110) +TEST_F(ValidationExamplesCase, CounterExample121) { - // http://spec.graphql.org/June2018/#example-00fbf + // https://spec.graphql.org/October2021/#example-00fbf auto query = R"(fragment conflictingArgsOnValues on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: HEEL) @@ -506,9 +502,9 @@ TEST_F(ValidationExamplesCase, CounterExample110) << "error should match"; } -TEST_F(ValidationExamplesCase, Example111) +TEST_F(ValidationExamplesCase, Example122) { - // http://spec.graphql.org/June2018/#example-a8406 + // https://spec.graphql.org/October2021/#example-a8406 auto query = R"(fragment safeDifferingFields on Pet { ... on Dog { volume: barkVolume @@ -539,9 +535,9 @@ TEST_F(ValidationExamplesCase, Example111) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample112) +TEST_F(ValidationExamplesCase, CounterExample123) { - // http://spec.graphql.org/June2018/#example-54e3d + // https://spec.graphql.org/October2021/#example-54e3d auto query = R"(fragment conflictingDifferingResponses on Pet { ... on Dog { someValue: nickname @@ -562,9 +558,9 @@ TEST_F(ValidationExamplesCase, CounterExample112) << "error should match"; } -TEST_F(ValidationExamplesCase, Example113) +TEST_F(ValidationExamplesCase, Example124) { - // http://spec.graphql.org/June2018/#example-e23c5 + // https://spec.graphql.org/October2021/#example-e23c5 auto query = R"(fragment scalarSelection on Dog { barkVolume } @@ -580,9 +576,9 @@ TEST_F(ValidationExamplesCase, Example113) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample114) +TEST_F(ValidationExamplesCase, CounterExample125) { - // http://spec.graphql.org/June2018/#example-13b69 + // https://spec.graphql.org/October2021/#example-13b69 auto query = R"(fragment scalarSelectionsNotAllowedOnInt on Dog { barkVolume { sinceWhen @@ -600,9 +596,9 @@ TEST_F(ValidationExamplesCase, CounterExample114) << "error should match"; } -TEST_F(ValidationExamplesCase, Example115) +TEST_F(ValidationExamplesCase, Example126) { - // http://spec.graphql.org/June2018/#example-9bada + // https://spec.graphql.org/October2021/#example-9bada auto query = R"(query { human { name @@ -625,9 +621,9 @@ TEST_F(ValidationExamplesCase, Example115) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample116) +TEST_F(ValidationExamplesCase, CounterExample127) { - // http://spec.graphql.org/June2018/#example-d68ee + // https://spec.graphql.org/October2021/#example-d68ee auto query = R"(query directQueryOnObjectWithoutSubFields { human } @@ -658,9 +654,9 @@ TEST_F(ValidationExamplesCase, CounterExample116) << "error should match"; } -TEST_F(ValidationExamplesCase, Example117) +TEST_F(ValidationExamplesCase, Example128) { - // http://spec.graphql.org/June2018/#example-760cb + // https://spec.graphql.org/October2021/#example-dfd15 auto query = R"(fragment argOnRequiredArg on Dog { doesKnowCommand(dogCommand: SIT) } @@ -681,9 +677,9 @@ TEST_F(ValidationExamplesCase, Example117) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample118) +TEST_F(ValidationExamplesCase, CounterExample129) { - // http://spec.graphql.org/June2018/#example-d5639 + // https://spec.graphql.org/October2021/#example-d5639 auto query = R"(fragment invalidArgName on Dog { doesKnowCommand(command: CLEAN_UP_HOUSE) })"_graphql; @@ -699,9 +695,9 @@ TEST_F(ValidationExamplesCase, CounterExample118) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample119) +TEST_F(ValidationExamplesCase, CounterExample130) { - // http://spec.graphql.org/June2018/#example-4feee + // https://spec.graphql.org/October2021/#example-df41e auto query = R"(fragment invalidArgName on Dog { isHousetrained(atOtherHomes: true) @include(unless: false) })"_graphql; @@ -717,9 +713,9 @@ TEST_F(ValidationExamplesCase, CounterExample119) << "error should match"; } -TEST_F(ValidationExamplesCase, Example120) +TEST_F(ValidationExamplesCase, Example131) { - // http://spec.graphql.org/June2018/#example-1891c + // https://spec.graphql.org/October2021/#example-73706 auto query = R"(query { arguments { multipleReqs(x: 1, y: 2) @@ -731,9 +727,9 @@ TEST_F(ValidationExamplesCase, Example120) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example121) +TEST_F(ValidationExamplesCase, Example132) { - // http://spec.graphql.org/June2018/#example-18fab + // https://spec.graphql.org/October2021/#example-bda7e auto query = R"(fragment multipleArgs on Arguments { multipleReqs(x: 1, y: 2) } @@ -759,9 +755,9 @@ TEST_F(ValidationExamplesCase, Example121) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example122) +TEST_F(ValidationExamplesCase, Example133) { - // http://spec.graphql.org/June2018/#example-503bd + // https://spec.graphql.org/October2021/#example-503bd auto query = R"(fragment goodBooleanArg on Arguments { booleanArgField(booleanArg: true) } @@ -782,9 +778,9 @@ TEST_F(ValidationExamplesCase, Example122) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example123) +TEST_F(ValidationExamplesCase, Example134) { - // http://spec.graphql.org/June2018/#example-1f1d2 + // https://spec.graphql.org/October2021/#example-1f1d2 auto query = R"(fragment goodBooleanArgDefault on Arguments { booleanArgField } @@ -800,9 +796,9 @@ TEST_F(ValidationExamplesCase, Example123) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample124) +TEST_F(ValidationExamplesCase, CounterExample135) { - // http://spec.graphql.org/June2018/#example-f12a1 + // https://spec.graphql.org/October2021/#example-f12a1 auto query = R"(fragment missingRequiredArg on Arguments { nonNullBooleanArgField })"_graphql; @@ -818,9 +814,9 @@ TEST_F(ValidationExamplesCase, CounterExample124) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample125) +TEST_F(ValidationExamplesCase, CounterExample136) { - // http://spec.graphql.org/June2018/#example-0bc81 + // https://spec.graphql.org/October2021/#example-0bc81 auto query = R"(fragment missingRequiredArg on Arguments { nonNullBooleanArgField(nonNullBooleanArg: null) })"_graphql; @@ -836,9 +832,9 @@ TEST_F(ValidationExamplesCase, CounterExample125) << "error should match"; } -TEST_F(ValidationExamplesCase, Example126) +TEST_F(ValidationExamplesCase, Example137) { - // http://spec.graphql.org/June2018/#example-3703b + // https://spec.graphql.org/October2021/#example-3703b auto query = R"({ dog { ...fragmentOne @@ -861,9 +857,9 @@ TEST_F(ValidationExamplesCase, Example126) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample127) +TEST_F(ValidationExamplesCase, CounterExample138) { - // http://spec.graphql.org/June2018/#example-2c3e3 + // https://spec.graphql.org/October2021/#example-2c3e3 auto query = R"({ dog { ...fragmentOne @@ -891,9 +887,9 @@ TEST_F(ValidationExamplesCase, CounterExample127) << "error should match"; } -TEST_F(ValidationExamplesCase, Example128) +TEST_F(ValidationExamplesCase, Example139) { - // http://spec.graphql.org/June2018/#example-1b2da + // https://spec.graphql.org/October2021/#example-1b2da auto query = R"(fragment correctType on Dog { name } @@ -923,9 +919,9 @@ TEST_F(ValidationExamplesCase, Example128) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample129) +TEST_F(ValidationExamplesCase, CounterExample140) { - // http://spec.graphql.org/June2018/#example-463f6 + // https://spec.graphql.org/October2021/#example-463f6 auto query = R"(fragment notOnExistingType on NotInSchema { name } @@ -951,9 +947,9 @@ TEST_F(ValidationExamplesCase, CounterExample129) << "error should match"; } -TEST_F(ValidationExamplesCase, Example130) +TEST_F(ValidationExamplesCase, Example141) { - // http://spec.graphql.org/June2018/#example-3c8d4 + // https://spec.graphql.org/October2021/#example-3c8d4 auto query = R"(fragment fragOnObject on Dog { name } @@ -981,9 +977,9 @@ TEST_F(ValidationExamplesCase, Example130) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample131) +TEST_F(ValidationExamplesCase, CounterExample142) { - // http://spec.graphql.org/June2018/#example-4d5e5 + // https://spec.graphql.org/October2021/#example-4d5e5 auto query = R"(fragment fragOnScalar on Int { something } @@ -1009,9 +1005,9 @@ TEST_F(ValidationExamplesCase, CounterExample131) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample132) +TEST_F(ValidationExamplesCase, CounterExample143) { - // http://spec.graphql.org/June2018/#example-9e1e3 + // https://spec.graphql.org/October2021/#example-9e1e3 auto query = R"(fragment nameFragment on Dog { # unused name } @@ -1033,9 +1029,9 @@ TEST_F(ValidationExamplesCase, CounterExample132) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample133) +TEST_F(ValidationExamplesCase, CounterExample144) { - // http://spec.graphql.org/June2018/#example-28421 + // https://spec.graphql.org/October2021/#example-28421 auto query = R"({ dog { ...undefinedFragment @@ -1053,9 +1049,9 @@ TEST_F(ValidationExamplesCase, CounterExample133) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample134) +TEST_F(ValidationExamplesCase, CounterExample145) { - // http://spec.graphql.org/June2018/#example-9ceb4 + // https://spec.graphql.org/October2021/#example-9ceb4 auto query = R"({ dog { ...nameFragment @@ -1087,9 +1083,9 @@ TEST_F(ValidationExamplesCase, CounterExample134) << "error should match"; } -TEST_F(ValidationExamplesCase, Example135) +TEST_F(ValidationExamplesCase, Example146) { - // http://spec.graphql.org/June2018/#example-08734 + // https://spec.graphql.org/October2021/#example-08734 auto query = R"({ dog { name @@ -1108,9 +1104,9 @@ TEST_F(ValidationExamplesCase, Example135) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample136) +TEST_F(ValidationExamplesCase, CounterExample147) { - // http://spec.graphql.org/June2018/#example-6bbad + // https://spec.graphql.org/October2021/#example-cd11c auto query = R"({ dog { ...dogFragment @@ -1148,9 +1144,9 @@ TEST_F(ValidationExamplesCase, CounterExample136) << "error should match"; } -TEST_F(ValidationExamplesCase, Example137) +TEST_F(ValidationExamplesCase, Example148) { - // http://spec.graphql.org/June2018/#example-0fc38 + // https://spec.graphql.org/October2021/#example-0fc38 auto query = R"(fragment dogFragment on Dog { ... on Dog { barkVolume @@ -1168,9 +1164,9 @@ TEST_F(ValidationExamplesCase, Example137) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample138) +TEST_F(ValidationExamplesCase, CounterExample149) { - // http://spec.graphql.org/June2018/#example-4d411 + // https://spec.graphql.org/October2021/#example-4d411 auto query = R"(fragment catInDogFragmentInvalid on Dog { ... on Cat { meowVolume @@ -1188,9 +1184,9 @@ TEST_F(ValidationExamplesCase, CounterExample138) << "error should match"; } -TEST_F(ValidationExamplesCase, Example139) +TEST_F(ValidationExamplesCase, Example150) { - // http://spec.graphql.org/June2018/#example-2c8d0 + // https://spec.graphql.org/October2021/#example-2c8d0 auto query = R"(fragment petNameFragment on Pet { name } @@ -1210,9 +1206,9 @@ TEST_F(ValidationExamplesCase, Example139) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example140) +TEST_F(ValidationExamplesCase, Example151) { - // http://spec.graphql.org/June2018/#example-41843 + // https://spec.graphql.org/October2021/#example-41843 auto query = R"(fragment catOrDogNameFragment on CatOrDog { ... on Cat { meowVolume @@ -1234,9 +1230,9 @@ TEST_F(ValidationExamplesCase, Example140) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example141) +TEST_F(ValidationExamplesCase, Example152) { - // http://spec.graphql.org/June2018/#example-85110 + // https://spec.graphql.org/October2021/#example-85110 auto query = R"(fragment petFragment on Pet { name ... on Dog { @@ -1262,9 +1258,9 @@ TEST_F(ValidationExamplesCase, Example141) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample142) +TEST_F(ValidationExamplesCase, CounterExample153) { - // http://spec.graphql.org/June2018/#example-a8dcc + // https://spec.graphql.org/October2021/#example-a8dcc auto query = R"(fragment sentientFragment on Sentient { ... on Dog { barkVolume @@ -1292,9 +1288,9 @@ TEST_F(ValidationExamplesCase, CounterExample142) << "error should match"; } -TEST_F(ValidationExamplesCase, Example143) +TEST_F(ValidationExamplesCase, Example154) { - // http://spec.graphql.org/June2018/#example-dc875 + // https://spec.graphql.org/October2021/#example-dc875 auto query = R"(fragment unionWithInterface on Pet { ...dogOrHumanFragment } @@ -1316,9 +1312,9 @@ TEST_F(ValidationExamplesCase, Example143) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample144) +TEST_F(ValidationExamplesCase, CounterExample155) { - // http://spec.graphql.org/June2018/#example-c9c63 + // https://spec.graphql.org/October2021/#example-c9c63 auto query = R"(fragment nonIntersectingInterfaces on Pet { ...sentientFragment } @@ -1338,9 +1334,31 @@ TEST_F(ValidationExamplesCase, CounterExample144) << "error should match"; } -TEST_F(ValidationExamplesCase, Example145) +TEST_F(ValidationExamplesCase, Example156) +{ + // https://spec.graphql.org/October2021/#example-bc12a + auto query = R"(fragment interfaceWithInterface on Node { + ...resourceFragment + } + + fragment resourceFragment on Resource { + url + } + + query { + resource { + ...interfaceWithInterface + } + })"_graphql; + + auto errors = _service->validate(query); + + ASSERT_TRUE(errors.empty()); +} + +TEST_F(ValidationExamplesCase, Example157) { - // http://spec.graphql.org/June2018/#example-7ee0e + // https://spec.graphql.org/October2021/#example-7ee0e auto query = R"(fragment goodBooleanArg on Arguments { booleanArgField(booleanArg: true) } @@ -1366,9 +1384,9 @@ TEST_F(ValidationExamplesCase, Example145) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample146) +TEST_F(ValidationExamplesCase, CounterExample158) { - // http://spec.graphql.org/June2018/#example-3a7c1 + // https://spec.graphql.org/October2021/#example-3a7c1 auto query = R"(fragment stringIntoInt on Arguments { intArgField(intArg: "123") } @@ -1401,9 +1419,9 @@ TEST_F(ValidationExamplesCase, CounterExample146) << "error should match"; } -TEST_F(ValidationExamplesCase, Example147) +TEST_F(ValidationExamplesCase, Example159) { - // http://spec.graphql.org/June2018/#example-a940b + // https://spec.graphql.org/October2021/#example-a940b auto query = R"({ findDog(complex: { name: "Fido" }) { name @@ -1415,9 +1433,9 @@ TEST_F(ValidationExamplesCase, Example147) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample148) +TEST_F(ValidationExamplesCase, CounterExample160) { - // http://spec.graphql.org/June2018/#example-1a5f6 + // https://spec.graphql.org/October2021/#example-1a5f6 auto query = R"({ findDog(complex: { favoriteCookieFlavor: "Bacon" }) { name @@ -1439,9 +1457,9 @@ TEST_F(ValidationExamplesCase, CounterExample148) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample149) +TEST_F(ValidationExamplesCase, CounterExample161) { - // http://spec.graphql.org/June2018/#example-5d541 + // https://spec.graphql.org/October2021/#example-5d541 auto query = R"({ findDog(complex: { name: "Fido", name: "Fido" }) { name @@ -1459,9 +1477,9 @@ TEST_F(ValidationExamplesCase, CounterExample149) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample150) +TEST_F(ValidationExamplesCase, CounterExample162) { - // http://spec.graphql.org/June2018/#example-55f3f + // https://spec.graphql.org/October2021/#example-55f3f auto query = R"(query @skip(if: $foo) { dog { name @@ -1479,9 +1497,9 @@ TEST_F(ValidationExamplesCase, CounterExample150) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample151) +TEST_F(ValidationExamplesCase, CounterExample163) { - // http://spec.graphql.org/June2018/#example-b2e6c + // https://spec.graphql.org/October2021/#example-b2e6c auto query = R"(query ($foo: Boolean = true, $bar: Boolean = false) { dog @skip(if: $foo) @skip(if: $bar) { name @@ -1499,9 +1517,9 @@ TEST_F(ValidationExamplesCase, CounterExample151) << "error should match"; } -TEST_F(ValidationExamplesCase, Example152) +TEST_F(ValidationExamplesCase, Example164) { - // http://spec.graphql.org/June2018/#example-c5ee9 + // https://spec.graphql.org/October2021/#example-c5ee9 auto query = R"(query ($foo: Boolean = true, $bar: Boolean = false) { dog @skip(if: $foo) { name @@ -1516,9 +1534,9 @@ TEST_F(ValidationExamplesCase, Example152) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample153) +TEST_F(ValidationExamplesCase, CounterExample165) { - // http://spec.graphql.org/June2018/#example-b767a + // https://spec.graphql.org/October2021/#example-abc9c auto query = R"(query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) { dog { isHousetrained(atOtherHomes: $atOtherHomes) @@ -1536,9 +1554,9 @@ TEST_F(ValidationExamplesCase, CounterExample153) << "error should match"; } -TEST_F(ValidationExamplesCase, Example154) +TEST_F(ValidationExamplesCase, Example166) { - // http://spec.graphql.org/June2018/#example-6f6b9 + // https://spec.graphql.org/October2021/#example-54c93 auto query = R"(query A($atOtherHomes: Boolean) { ...HouseTrainedFragment } @@ -1558,9 +1576,9 @@ TEST_F(ValidationExamplesCase, Example154) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example155) +TEST_F(ValidationExamplesCase, Example167) { - // http://spec.graphql.org/June2018/#example-f3185 + // https://spec.graphql.org/October2021/#example-ce150 auto query = R"(query takesComplexInput($complexInput: ComplexInput) { findDog(complex: $complexInput) { name @@ -1572,9 +1590,9 @@ TEST_F(ValidationExamplesCase, Example155) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example156) +TEST_F(ValidationExamplesCase, Example168) { - // http://spec.graphql.org/June2018/#example-77f18 + // https://spec.graphql.org/October2021/#example-a4255 auto query = R"(query takesBoolean($atOtherHomes: Boolean) { dog { isHousetrained(atOtherHomes: $atOtherHomes) @@ -1596,9 +1614,9 @@ TEST_F(ValidationExamplesCase, Example156) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample157) +TEST_F(ValidationExamplesCase, CounterExample169) { - // http://spec.graphql.org/June2018/#example-aeba9 + // https://spec.graphql.org/October2021/#example-aeba9 auto query = R"(query takesCat($cat: Cat) { dog { name @@ -1646,9 +1664,9 @@ TEST_F(ValidationExamplesCase, CounterExample157) << "error should match"; } -TEST_F(ValidationExamplesCase, Example158) +TEST_F(ValidationExamplesCase, Example170) { - // http://spec.graphql.org/June2018/#example-a5099 + // https://spec.graphql.org/October2021/#example-38119 auto query = R"(query variableIsDefined($atOtherHomes: Boolean) { dog { isHousetrained(atOtherHomes: $atOtherHomes) @@ -1660,9 +1678,9 @@ TEST_F(ValidationExamplesCase, Example158) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample159) +TEST_F(ValidationExamplesCase, CounterExample171) { - // http://spec.graphql.org/June2018/#example-c8425 + // https://spec.graphql.org/October2021/#example-5ba94 auto query = R"(query variableIsNotDefined { dog { isHousetrained(atOtherHomes: $atOtherHomes) @@ -1680,9 +1698,9 @@ TEST_F(ValidationExamplesCase, CounterExample159) << "error should match"; } -TEST_F(ValidationExamplesCase, Example160) +TEST_F(ValidationExamplesCase, Example172) { - // http://spec.graphql.org/June2018/#example-f4a77 + // https://spec.graphql.org/October2021/#example-559c2 auto query = R"(query variableIsDefinedUsedInSingleFragment($atOtherHomes: Boolean) { dog { ...isHousetrainedFragment @@ -1698,9 +1716,9 @@ TEST_F(ValidationExamplesCase, Example160) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample161) +TEST_F(ValidationExamplesCase, CounterExample173) { - // http://spec.graphql.org/June2018/#example-8c8db + // https://spec.graphql.org/October2021/#example-93d3e auto query = R"(query variableIsNotDefinedUsedInSingleFragment { dog { ...isHousetrainedFragment @@ -1722,9 +1740,9 @@ TEST_F(ValidationExamplesCase, CounterExample161) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample162) +TEST_F(ValidationExamplesCase, CounterExample174) { - // http://spec.graphql.org/June2018/#example-7b65c + // https://spec.graphql.org/October2021/#example-ee7be auto query = R"(query variableIsNotDefinedUsedInNestedFragment { dog { ...outerHousetrainedFragment @@ -1750,9 +1768,9 @@ TEST_F(ValidationExamplesCase, CounterExample162) << "error should match"; } -TEST_F(ValidationExamplesCase, Example163) +TEST_F(ValidationExamplesCase, Example175) { - // http://spec.graphql.org/June2018/#example-84129 + // https://spec.graphql.org/October2021/#example-d601e auto query = R"(query housetrainedQueryOne($atOtherHomes: Boolean) { dog { ...isHousetrainedFragment @@ -1774,9 +1792,9 @@ TEST_F(ValidationExamplesCase, Example163) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample164) +TEST_F(ValidationExamplesCase, CounterExample176) { - // http://spec.graphql.org/June2018/#example-ef68a + // https://spec.graphql.org/October2021/#example-2b284 auto query = R"(query housetrainedQueryOne($atOtherHomes: Boolean) { dog { ...isHousetrainedFragment @@ -1804,9 +1822,9 @@ TEST_F(ValidationExamplesCase, CounterExample164) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample165) +TEST_F(ValidationExamplesCase, CounterExample177) { - // http://spec.graphql.org/June2018/#example-516af + // https://spec.graphql.org/October2021/#example-464b6 auto query = R"(query variableUnused($atOtherHomes: Boolean) { dog { isHousetrained @@ -1824,9 +1842,9 @@ TEST_F(ValidationExamplesCase, CounterExample165) << "error should match"; } -TEST_F(ValidationExamplesCase, Example166) +TEST_F(ValidationExamplesCase, Example178) { - // http://spec.graphql.org/June2018/#example-ed1fa + // https://spec.graphql.org/October2021/#example-6d4bb auto query = R"(query variableUsedInFragment($atOtherHomes: Boolean) { dog { ...isHousetrainedFragment @@ -1842,9 +1860,9 @@ TEST_F(ValidationExamplesCase, Example166) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample167) +TEST_F(ValidationExamplesCase, CounterExample179) { - // http://spec.graphql.org/June2018/#example-f6c72 + // https://spec.graphql.org/October2021/#example-a30e2 auto query = R"(query variableNotUsedWithinFragment($atOtherHomes: Boolean) { dog { ...isHousetrainedWithoutVariableFragment @@ -1866,9 +1884,9 @@ TEST_F(ValidationExamplesCase, CounterExample167) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample168) +TEST_F(ValidationExamplesCase, CounterExample180) { - // http://spec.graphql.org/June2018/#example-5593f + // https://spec.graphql.org/October2021/#example-e647f auto query = R"(query queryWithUsedVar($atOtherHomes: Boolean) { dog { ...isHousetrainedFragment @@ -1896,9 +1914,9 @@ TEST_F(ValidationExamplesCase, CounterExample168) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample169) +TEST_F(ValidationExamplesCase, CounterExample181) { - // http://spec.graphql.org/June2018/#example-2028e + // https://spec.graphql.org/October2021/#example-2028e auto query = R"(query intCannotGoIntoBoolean($intArg: Int) { arguments { booleanArgField(booleanArg: $intArg) @@ -1916,9 +1934,9 @@ TEST_F(ValidationExamplesCase, CounterExample169) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample170) +TEST_F(ValidationExamplesCase, CounterExample182) { - // http://spec.graphql.org/June2018/#example-8d369 + // https://spec.graphql.org/October2021/#example-8d369 auto query = R"(query booleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) { arguments { booleanArgField(booleanArg: $booleanListArg) @@ -1936,9 +1954,9 @@ TEST_F(ValidationExamplesCase, CounterExample170) << "error should match"; } -TEST_F(ValidationExamplesCase, CounterExample171) +TEST_F(ValidationExamplesCase, CounterExample183) { - // http://spec.graphql.org/June2018/#example-ed727 + // https://spec.graphql.org/October2021/#example-ed727 auto query = R"(query booleanArgQuery($booleanArg: Boolean) { arguments { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg) @@ -1956,9 +1974,9 @@ TEST_F(ValidationExamplesCase, CounterExample171) << "error should match"; } -TEST_F(ValidationExamplesCase, Example172) +TEST_F(ValidationExamplesCase, Example184) { - // http://spec.graphql.org/June2018/#example-c5959 + // https://spec.graphql.org/October2021/#example-c5959 auto query = R"(query nonNullListToList($nonNullBooleanList: [Boolean]!) { arguments { booleanListArgField(booleanListArg: $nonNullBooleanList) @@ -1970,9 +1988,9 @@ TEST_F(ValidationExamplesCase, Example172) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, CounterExample173) +TEST_F(ValidationExamplesCase, CounterExample185) { - // http://spec.graphql.org/June2018/#example-64255 + // https://spec.graphql.org/October2021/#example-64255 auto query = R"(query listToNonNullList($booleanList: [Boolean]) { arguments { nonNullBooleanListField(nonNullBooleanListArg: $booleanList) @@ -1990,9 +2008,9 @@ TEST_F(ValidationExamplesCase, CounterExample173) << "error should match"; } -TEST_F(ValidationExamplesCase, Example174) +TEST_F(ValidationExamplesCase, Example186) { - // http://spec.graphql.org/June2018/#example-0877c + // https://spec.graphql.org/October2021/#example-0877c auto query = R"(query booleanArgQueryWithDefault($booleanArg: Boolean) { arguments { optionalNonNullBooleanArgField(optionalBooleanArg: $booleanArg) @@ -2004,9 +2022,9 @@ TEST_F(ValidationExamplesCase, Example174) ASSERT_TRUE(errors.empty()); } -TEST_F(ValidationExamplesCase, Example175) +TEST_F(ValidationExamplesCase, Example187) { - // http://spec.graphql.org/June2018/#example-d24d9 + // https://spec.graphql.org/October2021/#example-d24d9 auto query = R"(query booleanArgQueryWithDefault($booleanArg: Boolean = true) { arguments { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg)