Skip to content

Commit 2796413

Browse files
committed
Validate fragment spread is possible
1 parent 27d0bcb commit 2796413

File tree

3 files changed

+314
-277
lines changed

3 files changed

+314
-277
lines changed

include/Validation.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,12 @@ class ValidateExecutableVisitor
154154
using FieldTypes = std::map<std::string, ValidateTypeField>;
155155
using TypeFields = std::map<std::string, FieldTypes>;
156156

157+
std::optional<introspection::TypeKind> getTypeKind(const std::string& name) const;
157158
std::optional<introspection::TypeKind> getScopedTypeKind() const;
159+
constexpr bool isScalarType(introspection::TypeKind kind);
160+
161+
bool matchesScopedType(const std::string& name) const;
162+
158163
TypeFields::const_iterator getScopedTypeFields();
159164
static std::string getFieldType(const FieldTypes& fields, const std::string& name);
160165
static std::string getWrappedFieldType(const FieldTypes& fields, const std::string& name);
@@ -179,9 +184,11 @@ class ValidateExecutableVisitor
179184
using Directives = std::map<std::string, ValidateDirective>;
180185
using ExecutableNodes = std::map<std::string, const peg::ast_node&>;
181186
using FragmentSet = std::unordered_set<std::string>;
187+
using MatchingTypes = std::map<std::string, std::set<std::string>>;
182188

183189
OperationTypes _operationTypes;
184190
TypeKinds _typeKinds;
191+
MatchingTypes _matchingTypes;
185192
Directives _directives;
186193

187194
ExecutableNodes _fragmentDefinitions;

src/Validation.cpp

Lines changed: 145 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <iostream>
99
#include <algorithm>
10+
#include <iterator>
1011

1112
namespace graphql::service {
1213

@@ -259,6 +260,9 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
259260
types {
260261
name
261262
kind
263+
possibleTypes {
264+
name
265+
}
262266
}
263267
directives {
264268
name
@@ -350,7 +354,59 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
350354
&& itrKind != typeMembers.end()
351355
&& itrKind->second.type() == response::Type::EnumValue)
352356
{
353-
_typeKinds[itrName->second.release<response::StringType>()] = ModifiedArgument<introspection::TypeKind>::convert(itrKind->second);
357+
auto name = itrName->second.release<response::StringType>();
358+
auto kind = ModifiedArgument<introspection::TypeKind>::convert(itrKind->second);
359+
360+
if (!isScalarType(kind))
361+
{
362+
if (kind == introspection::TypeKind::OBJECT)
363+
{
364+
_matchingTypes[name].insert(name);
365+
}
366+
else
367+
{
368+
auto itrPossibleTypes = std::find_if(typeMembers.begin(), typeMembers.end(),
369+
[](const std::pair<std::string, response::Value>& entry) noexcept
370+
{
371+
return entry.first == R"gql(possibleTypes)gql";
372+
});
373+
374+
if (itrPossibleTypes != typeMembers.end()
375+
&& itrPossibleTypes->second.type() == response::Type::List)
376+
{
377+
std::set<std::string> matchingTypes;
378+
auto matchingTypeEntries = itrPossibleTypes->second.release<response::ListType>();
379+
380+
for (auto& matchingTypeEntry : matchingTypeEntries)
381+
{
382+
if (matchingTypeEntry.type() != response::Type::Map)
383+
{
384+
continue;
385+
}
386+
387+
auto matchingTypeMembers = matchingTypeEntry.release<response::MapType>();
388+
auto itrMatchingTypeName = std::find_if(matchingTypeMembers.begin(), matchingTypeMembers.end(),
389+
[](const std::pair<std::string, response::Value>& entry) noexcept
390+
{
391+
return entry.first == R"gql(name)gql";
392+
});
393+
394+
if (itrMatchingTypeName != matchingTypeMembers.end()
395+
&& itrMatchingTypeName->second.type() == response::Type::String)
396+
{
397+
matchingTypes.insert(itrMatchingTypeName->second.release<response::StringType>());
398+
}
399+
}
400+
401+
if (!matchingTypes.empty())
402+
{
403+
_matchingTypes[name] = std::move(matchingTypes);
404+
}
405+
}
406+
}
407+
}
408+
409+
_typeKinds[std::move(name)] = kind;
354410
}
355411
}
356412
}
@@ -577,40 +633,23 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra
577633

578634
auto itrKind = _typeKinds.find(innerType);
579635

580-
if (itrKind == _typeKinds.end())
636+
if (itrKind == _typeKinds.end()
637+
|| isScalarType(itrKind->second))
581638
{
582639
// http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence
640+
// http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types
583641
auto position = typeCondition->begin();
584642
std::ostringstream message;
585643

586-
message << "Undefined target type on fragment definition: " << name
644+
message << (itrKind == _typeKinds.end()
645+
? "Undefined target type on fragment definition: "
646+
: "Scalar target type on fragment definition: ") << name
587647
<< " name: " << innerType;
588648

589649
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
590650
return;
591651
}
592652

593-
switch (itrKind->second)
594-
{
595-
case introspection::TypeKind::OBJECT:
596-
case introspection::TypeKind::INTERFACE:
597-
case introspection::TypeKind::UNION:
598-
break;
599-
600-
default:
601-
{
602-
// http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types
603-
auto position = typeCondition->begin();
604-
std::ostringstream message;
605-
606-
message << "Scalar target type on fragment definition: " << name
607-
<< " name: " << innerType;
608-
609-
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
610-
return;
611-
}
612-
}
613-
614653
_fragmentStack.insert(name);
615654
_scopedType = std::move(innerType);
616655

@@ -763,41 +802,67 @@ ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(response::Lis
763802
return result;
764803
}
765804

766-
std::optional<introspection::TypeKind> ValidateExecutableVisitor::getScopedTypeKind() const
805+
std::optional<introspection::TypeKind> ValidateExecutableVisitor::getTypeKind(const std::string& name) const
767806
{
768-
auto itrKind = _typeKinds.find(_scopedType);
807+
auto itrKind = _typeKinds.find(name);
769808

770809
return (itrKind == _typeKinds.cend()
771810
? std::nullopt
772811
: std::make_optional(itrKind->second));
773812
}
774813

775-
ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor::getScopedTypeFields()
814+
std::optional<introspection::TypeKind> ValidateExecutableVisitor::getScopedTypeKind() const
776815
{
777-
auto typeKind = getScopedTypeKind();
778-
auto itrType = _typeFields.find(_scopedType);
816+
return getTypeKind(_scopedType);
817+
}
779818

780-
if (itrType == _typeFields.cend())
819+
constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind kind)
820+
{
821+
switch (kind)
781822
{
782-
if (!typeKind)
783-
{
784-
return itrType;
785-
}
823+
case introspection::TypeKind::OBJECT:
824+
case introspection::TypeKind::INTERFACE:
825+
case introspection::TypeKind::UNION:
826+
return false;
827+
828+
default:
829+
return true;
830+
}
831+
}
832+
833+
bool ValidateExecutableVisitor::matchesScopedType(const std::string& name) const
834+
{
835+
if (name == _scopedType)
836+
{
837+
return true;
838+
}
839+
840+
auto itrScoped = _matchingTypes.find(_scopedType);
841+
auto itrNamed = _matchingTypes.find(name);
786842

787-
switch (*typeKind)
843+
if (itrScoped != _matchingTypes.end()
844+
&& itrNamed != _matchingTypes.end())
845+
{
846+
auto itrMatch = std::find_if(itrScoped->second.begin(), itrScoped->second.end(),
847+
[this, itrNamed](const std::string& matchingType) noexcept
788848
{
789-
case introspection::TypeKind::OBJECT:
790-
case introspection::TypeKind::INTERFACE:
791-
case introspection::TypeKind::UNION:
792-
// These are the only types which support sub-fields.
793-
break;
849+
return itrNamed->second.find(matchingType) != itrNamed->second.end();
850+
});
794851

795-
default:
796-
return itrType;
797-
}
852+
return itrMatch != itrScoped->second.end();
798853
}
799854

800-
if (itrType == _typeFields.cend())
855+
return false;
856+
}
857+
858+
ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor::getScopedTypeFields()
859+
{
860+
auto typeKind = getScopedTypeKind();
861+
auto itrType = _typeFields.find(_scopedType);
862+
863+
if (itrType == _typeFields.cend()
864+
&& typeKind
865+
&& !isScalarType(*typeKind))
801866
{
802867
std::ostringstream oss;
803868

@@ -1329,27 +1394,17 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field)
13291394
{
13301395
auto itrInnerKind = _typeKinds.find(innerType);
13311396

1332-
if (itrInnerKind != _typeKinds.end())
1397+
if (itrInnerKind != _typeKinds.end()
1398+
&& !isScalarType(itrInnerKind->second))
13331399
{
1334-
switch (itrInnerKind->second)
1335-
{
1336-
case introspection::TypeKind::OBJECT:
1337-
case introspection::TypeKind::INTERFACE:
1338-
case introspection::TypeKind::UNION:
1339-
{
1340-
// http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections
1341-
auto position = field.begin();
1342-
std::ostringstream message;
1343-
1344-
message << "Missing fields on non-scalar type: " << innerType;
1400+
// http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections
1401+
auto position = field.begin();
1402+
std::ostringstream message;
13451403

1346-
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
1347-
return;
1348-
}
1404+
message << "Missing fields on non-scalar type: " << innerType;
13491405

1350-
default:
1351-
break;
1352-
}
1406+
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
1407+
return;
13531408
}
13541409
}
13551410

@@ -1396,7 +1451,22 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen
13961451
});
13971452

13981453
const auto& selection = *itr->second.children.back();
1399-
std::string innerType{ itr->second.children[1]->children.front()->string_view() };
1454+
const auto& typeCondition = itr->second.children[1];
1455+
std::string innerType{ typeCondition->children.front()->string_view() };
1456+
1457+
if (!matchesScopedType(innerType))
1458+
{
1459+
// http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible
1460+
auto position = typeCondition->begin();
1461+
std::ostringstream message;
1462+
1463+
message << "Incompatible fragment spread target type: " << innerType
1464+
<< " name: " << name;
1465+
1466+
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
1467+
return;
1468+
}
1469+
14001470
auto outerType = std::move(_scopedType);
14011471

14021472
_fragmentStack.insert(name);
@@ -1432,34 +1502,30 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF
14321502
{
14331503
auto itrKind = _typeKinds.find(innerType);
14341504

1435-
if (_typeKinds.find(innerType) == _typeKinds.end())
1505+
if (itrKind == _typeKinds.end()
1506+
|| isScalarType(itrKind->second))
14361507
{
14371508
// http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence
1509+
// http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types
14381510
std::ostringstream message;
14391511

1440-
message << "Undefined target type on inline fragment name: " << innerType;
1512+
message << (itrKind == _typeKinds.end()
1513+
? "Undefined target type on inline fragment name: "
1514+
: "Scalar target type on inline fragment name: ") << innerType;
14411515

14421516
_errors.push_back({ message.str(), std::move(typeConditionLocation) });
14431517
return;
14441518
}
14451519

1446-
switch (itrKind->second)
1520+
if (!matchesScopedType(innerType))
14471521
{
1448-
case introspection::TypeKind::OBJECT:
1449-
case introspection::TypeKind::INTERFACE:
1450-
case introspection::TypeKind::UNION:
1451-
break;
1452-
1453-
default:
1454-
{
1455-
// http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types
1456-
std::ostringstream message;
1522+
// http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible
1523+
std::ostringstream message;
14571524

1458-
message << "Scalar target type on inline fragment name: " << innerType;
1525+
message << "Incompatible target type on inline fragment name: " << innerType;
14591526

1460-
_errors.push_back({ message.str(), std::move(typeConditionLocation) });
1461-
return;
1462-
}
1527+
_errors.push_back({ message.str(), std::move(typeConditionLocation) });
1528+
return;
14631529
}
14641530
}
14651531

0 commit comments

Comments
 (0)