|
7 | 7 |
|
8 | 8 | #include <iostream>
|
9 | 9 | #include <algorithm>
|
| 10 | +#include <iterator> |
10 | 11 |
|
11 | 12 | namespace graphql::service {
|
12 | 13 |
|
@@ -259,6 +260,9 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
|
259 | 260 | types {
|
260 | 261 | name
|
261 | 262 | kind
|
| 263 | + possibleTypes { |
| 264 | + name |
| 265 | + } |
262 | 266 | }
|
263 | 267 | directives {
|
264 | 268 | name
|
@@ -350,7 +354,59 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
|
350 | 354 | && itrKind != typeMembers.end()
|
351 | 355 | && itrKind->second.type() == response::Type::EnumValue)
|
352 | 356 | {
|
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; |
354 | 410 | }
|
355 | 411 | }
|
356 | 412 | }
|
@@ -577,40 +633,23 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra
|
577 | 633 |
|
578 | 634 | auto itrKind = _typeKinds.find(innerType);
|
579 | 635 |
|
580 |
| - if (itrKind == _typeKinds.end()) |
| 636 | + if (itrKind == _typeKinds.end() |
| 637 | + || isScalarType(itrKind->second)) |
581 | 638 | {
|
582 | 639 | // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence
|
| 640 | + // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types |
583 | 641 | auto position = typeCondition->begin();
|
584 | 642 | std::ostringstream message;
|
585 | 643 |
|
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 |
587 | 647 | << " name: " << innerType;
|
588 | 648 |
|
589 | 649 | _errors.push_back({ message.str(), { position.line, position.byte_in_line } });
|
590 | 650 | return;
|
591 | 651 | }
|
592 | 652 |
|
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 |
| - |
614 | 653 | _fragmentStack.insert(name);
|
615 | 654 | _scopedType = std::move(innerType);
|
616 | 655 |
|
@@ -763,41 +802,67 @@ ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(response::Lis
|
763 | 802 | return result;
|
764 | 803 | }
|
765 | 804 |
|
766 |
| -std::optional<introspection::TypeKind> ValidateExecutableVisitor::getScopedTypeKind() const |
| 805 | +std::optional<introspection::TypeKind> ValidateExecutableVisitor::getTypeKind(const std::string& name) const |
767 | 806 | {
|
768 |
| - auto itrKind = _typeKinds.find(_scopedType); |
| 807 | + auto itrKind = _typeKinds.find(name); |
769 | 808 |
|
770 | 809 | return (itrKind == _typeKinds.cend()
|
771 | 810 | ? std::nullopt
|
772 | 811 | : std::make_optional(itrKind->second));
|
773 | 812 | }
|
774 | 813 |
|
775 |
| -ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor::getScopedTypeFields() |
| 814 | +std::optional<introspection::TypeKind> ValidateExecutableVisitor::getScopedTypeKind() const |
776 | 815 | {
|
777 |
| - auto typeKind = getScopedTypeKind(); |
778 |
| - auto itrType = _typeFields.find(_scopedType); |
| 816 | + return getTypeKind(_scopedType); |
| 817 | +} |
779 | 818 |
|
780 |
| - if (itrType == _typeFields.cend()) |
| 819 | +constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind kind) |
| 820 | +{ |
| 821 | + switch (kind) |
781 | 822 | {
|
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); |
786 | 842 |
|
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 |
788 | 848 | {
|
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 | + }); |
794 | 851 |
|
795 |
| - default: |
796 |
| - return itrType; |
797 |
| - } |
| 852 | + return itrMatch != itrScoped->second.end(); |
798 | 853 | }
|
799 | 854 |
|
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)) |
801 | 866 | {
|
802 | 867 | std::ostringstream oss;
|
803 | 868 |
|
@@ -1329,27 +1394,17 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field)
|
1329 | 1394 | {
|
1330 | 1395 | auto itrInnerKind = _typeKinds.find(innerType);
|
1331 | 1396 |
|
1332 |
| - if (itrInnerKind != _typeKinds.end()) |
| 1397 | + if (itrInnerKind != _typeKinds.end() |
| 1398 | + && !isScalarType(itrInnerKind->second)) |
1333 | 1399 | {
|
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; |
1345 | 1403 |
|
1346 |
| - _errors.push_back({ message.str(), { position.line, position.byte_in_line } }); |
1347 |
| - return; |
1348 |
| - } |
| 1404 | + message << "Missing fields on non-scalar type: " << innerType; |
1349 | 1405 |
|
1350 |
| - default: |
1351 |
| - break; |
1352 |
| - } |
| 1406 | + _errors.push_back({ message.str(), { position.line, position.byte_in_line } }); |
| 1407 | + return; |
1353 | 1408 | }
|
1354 | 1409 | }
|
1355 | 1410 |
|
@@ -1396,7 +1451,22 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen
|
1396 | 1451 | });
|
1397 | 1452 |
|
1398 | 1453 | 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 | + |
1400 | 1470 | auto outerType = std::move(_scopedType);
|
1401 | 1471 |
|
1402 | 1472 | _fragmentStack.insert(name);
|
@@ -1432,34 +1502,30 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF
|
1432 | 1502 | {
|
1433 | 1503 | auto itrKind = _typeKinds.find(innerType);
|
1434 | 1504 |
|
1435 |
| - if (_typeKinds.find(innerType) == _typeKinds.end()) |
| 1505 | + if (itrKind == _typeKinds.end() |
| 1506 | + || isScalarType(itrKind->second)) |
1436 | 1507 | {
|
1437 | 1508 | // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence
|
| 1509 | + // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types |
1438 | 1510 | std::ostringstream message;
|
1439 | 1511 |
|
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; |
1441 | 1515 |
|
1442 | 1516 | _errors.push_back({ message.str(), std::move(typeConditionLocation) });
|
1443 | 1517 | return;
|
1444 | 1518 | }
|
1445 | 1519 |
|
1446 |
| - switch (itrKind->second) |
| 1520 | + if (!matchesScopedType(innerType)) |
1447 | 1521 | {
|
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; |
1457 | 1524 |
|
1458 |
| - message << "Scalar target type on inline fragment name: " << innerType; |
| 1525 | + message << "Incompatible target type on inline fragment name: " << innerType; |
1459 | 1526 |
|
1460 |
| - _errors.push_back({ message.str(), std::move(typeConditionLocation) }); |
1461 |
| - return; |
1462 |
| - } |
| 1527 | + _errors.push_back({ message.str(), std::move(typeConditionLocation) }); |
| 1528 | + return; |
1463 | 1529 | }
|
1464 | 1530 | }
|
1465 | 1531 |
|
|
0 commit comments