Skip to content

Implement the Validation section of the spec #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Mar 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f4c0931
Add a unit test for missing types in LookupType
wravery Feb 15, 2020
359d835
Validate framgent name uniqueness
wravery Feb 15, 2020
fea2afd
Expose schema to Request for validation
wravery Feb 22, 2020
145dc34
Validate no type definitions in executable doc
wravery Feb 22, 2020
1728b63
Validate unique operation names and lone anon
wravery Feb 22, 2020
35542d9
Validate subscription single root field
wravery Feb 23, 2020
3d96580
Validate leaf field selections
wravery Feb 23, 2020
f3b9259
Include position in error for missing leaf fields
wravery Feb 23, 2020
a1e6e43
Optional async subscription delivery
wravery Feb 23, 2020
85def36
Put location information in separate error field
wravery Feb 23, 2020
b9ce1ff
Enable parallel subscription callbacks
wravery Feb 23, 2020
2e9e9b5
Improve errors with location and path
wravery Feb 24, 2020
ef2e765
Add a mutex in resolvers around field accessors
wravery Feb 25, 2020
4e4a29c
Add unit tests for async launch policy
wravery Feb 25, 2020
b7325e7
VS formatting, code analysis fixes, and race condition fix in unit test
wravery Feb 26, 2020
a8aa5df
Turn long lambdas into named private methods
wravery Feb 26, 2020
f2d98a9
Start building a separate validation API and tests
wravery Feb 28, 2020
d74c115
Add default schema operation type detection
wravery Feb 28, 2020
7622979
Add validation with unit tests based on spec examples
wravery Mar 1, 2020
8d73ec4
Make sure to extract the types list in ValidateExecutableVisitor
wravery Mar 1, 2020
4762b40
Add field selection merging and example-based unit tests
wravery Mar 3, 2020
88b290e
Block re-resolving duplicate fields
wravery Mar 3, 2020
1e11c78
Implement leaf field selections validation
wravery Mar 3, 2020
cb382d6
Fix URL in test comment
wravery Mar 3, 2020
98e912c
Fix some errors in the validation schema and add descriptions
wravery Mar 3, 2020
c9a4bb5
Add built-in directives to the Introspection schema
wravery Mar 3, 2020
bd9149b
Add validation for argument name existence
wravery Mar 3, 2020
eb66e7c
Implement argument validation
wravery Mar 4, 2020
da9814a
Differentiate between missing and required non-null args
wravery Mar 4, 2020
4c2310f
Implement and test cyclic fragment validation
wravery Mar 4, 2020
f254b29
Switch to resolving against peg::ast and tracking whether or not it's…
wravery Mar 4, 2020
dcbe253
Update sample program to perform validation
wravery Mar 4, 2020
8bfefdc
Fix signed/unsigned mismatch on x86 Windows
wravery Mar 4, 2020
0bc5c6c
Split validation into separate files to prep for refactoring
wravery Mar 4, 2020
27d0bcb
Add possibleTypes to interfaces in Introspection
wravery Mar 4, 2020
2796413
Validate fragment spread is possible
wravery Mar 5, 2020
390e613
Correct example 140 unittest which should be valid
wravery Mar 5, 2020
1e215b4
Finish fragment validation unit tests
wravery Mar 5, 2020
b12a87a
Implement Value validation
wravery Mar 7, 2020
60589e1
Implement Directive validation
wravery Mar 7, 2020
52d8175
Finish implementing Variable validation
wravery Mar 8, 2020
4a7c77d
Distinguish between null and non-null default values
wravery Mar 8, 2020
1c69481
Fix compile error on clang for WSL
wravery Mar 8, 2020
d79d6b6
Fix handling of std::vector<bool> specialization
wravery Mar 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions include/SchemaGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#ifndef SCHEMAGENERATOR_H
#define SCHEMAGENERATOR_H

#include <graphqlservice/GraphQLService.h>
#include <graphqlservice/GraphQLGrammar.h>
#include "graphqlservice/GraphQLService.h"
#include "graphqlservice/GraphQLGrammar.h"

#include <array>
#include <cstdio>
Expand Down Expand Up @@ -291,6 +291,12 @@ class Generator
std::vector<std::string> Build() const noexcept;

private:
std::string getHeaderDir() const noexcept;
std::string getSourceDir() const noexcept;
std::string getHeaderPath() const noexcept;
std::string getObjectHeaderPath() const noexcept;
std::string getSourcePath() const noexcept;

void visitDefinition(const peg::ast_node& definition);

void visitSchemaDefinition(const peg::ast_node& schemaDefinition);
Expand Down
257 changes: 257 additions & 0 deletions include/Validation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

#ifndef VALIDATION_H
#define VALIDATION_H

#include "graphqlservice/GraphQLService.h"
#include "graphqlservice/IntrospectionSchema.h"

namespace graphql::service {

using ValidateType = response::Value;

struct ValidateArgument
{
bool defaultValue = false;
bool nonNullDefaultValue = false;
ValidateType type;
};

using ValidateTypeFieldArguments = std::map<std::string, ValidateArgument>;

struct ValidateTypeField
{
ValidateType returnType;
ValidateTypeFieldArguments arguments;
};

using ValidateDirectiveArguments = std::map<std::string, ValidateArgument>;

struct ValidateDirective
{
std::set<introspection::DirectiveLocation> locations;
ValidateDirectiveArguments arguments;
};

struct ValidateArgumentVariable
{
bool operator==(const ValidateArgumentVariable& other) const;

std::string name;
};

struct ValidateArgumentEnumValue
{
bool operator==(const ValidateArgumentEnumValue& other) const;

std::string value;
};

struct ValidateArgumentValue;

struct ValidateArgumentValuePtr
{
bool operator==(const ValidateArgumentValuePtr& other) const;

std::unique_ptr<ValidateArgumentValue> value;
schema_location position;
};

struct ValidateArgumentList
{
bool operator==(const ValidateArgumentList& other) const;

std::vector<ValidateArgumentValuePtr> values;
};

struct ValidateArgumentMap
{
bool operator==(const ValidateArgumentMap& other) const;

std::map<std::string, ValidateArgumentValuePtr> values;
};

using ValidateArgumentVariant = std::variant<
ValidateArgumentVariable,
response::IntType,
response::FloatType,
response::StringType,
response::BooleanType,
ValidateArgumentEnumValue,
ValidateArgumentList,
ValidateArgumentMap>;

struct ValidateArgumentValue
{
ValidateArgumentValue(ValidateArgumentVariable&& value);
ValidateArgumentValue(response::IntType value);
ValidateArgumentValue(response::FloatType value);
ValidateArgumentValue(response::StringType&& value);
ValidateArgumentValue(response::BooleanType value);
ValidateArgumentValue(ValidateArgumentEnumValue&& value);
ValidateArgumentValue(ValidateArgumentList&& value);
ValidateArgumentValue(ValidateArgumentMap&& value);

ValidateArgumentVariant data;
};

// ValidateArgumentValueVisitor visits the AST and builds a record of a field return type and map
// of the arguments for comparison to see if 2 fields with the same result name can be merged.
class ValidateArgumentValueVisitor
{
public:
ValidateArgumentValueVisitor(std::vector<schema_error>& errors);

void visit(const peg::ast_node& value);

ValidateArgumentValuePtr getArgumentValue();

private:
void visitVariable(const peg::ast_node& variable);
void visitIntValue(const peg::ast_node& intValue);
void visitFloatValue(const peg::ast_node& floatValue);
void visitStringValue(const peg::ast_node& stringValue);
void visitBooleanValue(const peg::ast_node& booleanValue);
void visitNullValue(const peg::ast_node& nullValue);
void visitEnumValue(const peg::ast_node& enumValue);
void visitListValue(const peg::ast_node& listValue);
void visitObjectValue(const peg::ast_node& objectValue);

ValidateArgumentValuePtr _argumentValue;
std::vector<schema_error>& _errors;
};

using ValidateFieldArguments = std::map<std::string, ValidateArgumentValuePtr>;

struct ValidateField
{
ValidateField(std::string&& returnType, std::optional<std::string>&& objectType, const std::string& fieldName, ValidateFieldArguments&& arguments);

bool operator==(const ValidateField& other) const;

std::string returnType;
std::optional<std::string> objectType;
std::string fieldName;
ValidateFieldArguments arguments;
};

using ValidateTypeKinds = std::map<std::string, introspection::TypeKind>;

// ValidateVariableTypeVisitor visits the AST and builds a ValidateType structure representing
// a variable type in an operation definition as if it came from an Introspection query.
class ValidateVariableTypeVisitor
{
public:
ValidateVariableTypeVisitor(const ValidateTypeKinds& typeKinds);

void visit(const peg::ast_node& typeName);

bool isInputType() const;
ValidateType getType();

private:
void visitNamedType(const peg::ast_node& namedType);
void visitListType(const peg::ast_node& listType);
void visitNonNullType(const peg::ast_node& nonNullType);

const ValidateTypeKinds& _typeKinds;

bool _isInputType = false;
ValidateType _variableType;
};

// ValidateExecutableVisitor visits the AST and validates that it is executable against the service schema.
class ValidateExecutableVisitor
{
public:
ValidateExecutableVisitor(const Request& service);

void visit(const peg::ast_node& root);

std::vector<schema_error> getStructuredErrors();

private:
response::Value executeQuery(std::string_view query) const;

static ValidateTypeFieldArguments getArguments(response::ListType&& argumentsMember);

using FieldTypes = std::map<std::string, ValidateTypeField>;
using TypeFields = std::map<std::string, FieldTypes>;
using InputFieldTypes = ValidateTypeFieldArguments;
using InputTypeFields = std::map<std::string, InputFieldTypes>;
using EnumValues = std::map<std::string, std::set<std::string>>;

std::optional<introspection::TypeKind> getTypeKind(const std::string& name) const;
std::optional<introspection::TypeKind> getScopedTypeKind() const;
constexpr bool isScalarType(introspection::TypeKind kind);

bool matchesScopedType(const std::string& name) const;

TypeFields::const_iterator getScopedTypeFields();
InputTypeFields::const_iterator getInputTypeFields(const std::string& name);
static const ValidateType& getValidateFieldType(const FieldTypes::mapped_type& value);
static const ValidateType& getValidateFieldType(const InputFieldTypes::mapped_type& value);
template <class _FieldTypes>
static std::string getFieldType(const _FieldTypes& fields, const std::string& name);
template <class _FieldTypes>
static std::string getWrappedFieldType(const _FieldTypes& fields, const std::string& name);
static std::string getWrappedFieldType(const ValidateType& returnType);

void visitFragmentDefinition(const peg::ast_node& fragmentDefinition);
void visitOperationDefinition(const peg::ast_node& operationDefinition);

void visitSelection(const peg::ast_node& selection);

void visitField(const peg::ast_node& field);
void visitFragmentSpread(const peg::ast_node& fragmentSpread);
void visitInlineFragment(const peg::ast_node& inlineFragment);

void visitDirectives(introspection::DirectiveLocation location, const peg::ast_node& directives);

bool validateInputValue(bool hasNonNullDefaultValue, const ValidateArgumentValuePtr& argument, const ValidateType& type);
bool validateVariableType(bool isNonNull, const ValidateType& variableType, const schema_location& position, const ValidateType& inputType);


const Request& _service;
std::vector<schema_error> _errors;

using OperationTypes = std::map<std::string_view, std::string>;
using Directives = std::map<std::string, ValidateDirective>;
using ExecutableNodes = std::map<std::string, const peg::ast_node&>;
using FragmentSet = std::unordered_set<std::string>;
using MatchingTypes = std::map<std::string, std::set<std::string>>;
using ScalarTypes = std::set<std::string>;
using VariableDefinitions = std::map<std::string, const peg::ast_node&>;
using VariableTypes = std::map<std::string, ValidateArgument>;
using OperationVariables = std::optional<VariableTypes>;
using VariableSet = std::set<std::string>;

OperationTypes _operationTypes;
ValidateTypeKinds _typeKinds;
MatchingTypes _matchingTypes;
Directives _directives;
EnumValues _enumValues;
ScalarTypes _scalarTypes;

ExecutableNodes _fragmentDefinitions;
ExecutableNodes _operationDefinitions;

OperationVariables _operationVariables;
VariableDefinitions _variableDefinitions;
VariableSet _referencedVariables;
FragmentSet _referencedFragments;
FragmentSet _fragmentStack;
FragmentSet _fragmentCycles;
size_t _fieldCount = 0;
TypeFields _typeFields;
InputTypeFields _inputTypeFields;
std::string _scopedType;
std::map<std::string, ValidateField> _selectionFields;
};

} /* namespace graphql::service */

#endif // VALIDATION_H
2 changes: 1 addition & 1 deletion include/graphqlservice/GraphQLGrammar.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#ifndef GRAPHQLGRAMMAR_H
#define GRAPHQLGRAMMAR_H

#include <graphqlservice/GraphQLTree.h>
#include "graphqlservice/GraphQLTree.h"

#define TAO_PEGTL_NAMESPACE tao::graphqlpeg

Expand Down
1 change: 1 addition & 0 deletions include/graphqlservice/GraphQLParse.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct ast
{
std::shared_ptr<ast_input> input;
std::shared_ptr<ast_node> root;
bool validated = false;
};

ast parseString(std::string_view input);
Expand Down
8 changes: 4 additions & 4 deletions include/graphqlservice/GraphQLResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,24 @@ struct ValueTypeTraits
// Set by r-value reference, get by const reference, and release by value. The only types
// that actually support all 3 methods are StringType and ScalarType, everything else
// overrides some subset of these types with a template specialization.
using set_type = ValueType &&;
using get_type = const ValueType &;
using set_type = ValueType&&;
using get_type = const ValueType&;
using release_type = ValueType;
};

template <>
struct ValueTypeTraits<MapType>
{
// Get by const reference and release by value.
using get_type = const MapType &;
using get_type = const MapType&;
using release_type = MapType;
};

template <>
struct ValueTypeTraits<ListType>
{
// Get by const reference and release by value.
using get_type = const ListType &;
using get_type = const ListType&;
using release_type = ListType;
};

Expand Down
Loading