Autogenerated
2 days ago
GraphQLEndpointRegistrar.php
2 months ago
OpcacheFileExpiry.php
2 months ago
QueryCache.php
2 months ago
QueryComplexityRule.php
2 days ago
QueryDepthRule.php
2 days ago
Settings.php
2 months ago
StatusResolverFailedException.php
2 months ago
QueryDepthRule.php
103 lines
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Automattic\WooCommerce\Internal\Api; |
| 6 | |
| 7 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FragmentSpreadNode; |
| 8 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\Node; |
| 9 | use Automattic\WooCommerce\Vendor\GraphQL\Validator\QueryValidationContext; |
| 10 | use Automattic\WooCommerce\Vendor\GraphQL\Validator\Rules\QueryDepth; |
| 11 | |
| 12 | /** |
| 13 | * QueryDepth validation rule that returns a generic error message when the |
| 14 | * depth is exceeded. Admins can still read both values via debug mode; see |
| 15 | * {@see GraphQLController} step 8. |
| 16 | * |
| 17 | * Unlike the stock webonyx rule, which walks a named fragment again on every |
| 18 | * spread, each fragment's depth is computed once, relative to the position it |
| 19 | * is spread at, and reused. |
| 20 | */ |
| 21 | class QueryDepthRule extends QueryDepth { |
| 22 | /** |
| 23 | * Sentinel for a selection tree with no nested selection sets, which adds |
| 24 | * no depth wherever it is spread. The stock walk only ever raises the |
| 25 | * running maximum, so seeding it with -1 makes the same walk report either |
| 26 | * the relative depth (>= 0) or this sentinel. |
| 27 | */ |
| 28 | private const NO_NESTED_FIELDS = -1; |
| 29 | |
| 30 | /** |
| 31 | * Memoized relative depth of each named fragment, keyed by fragment name. |
| 32 | * |
| 33 | * @var array<string, int> |
| 34 | */ |
| 35 | private array $fragment_depths = array(); |
| 36 | |
| 37 | /** |
| 38 | * Reset the per-document memoization before delegating to the stock visitor. |
| 39 | * |
| 40 | * @param QueryValidationContext $context The validation context. |
| 41 | * @return array The visitor definition. |
| 42 | */ |
| 43 | public function getVisitor( QueryValidationContext $context ): array { |
| 44 | $this->fragment_depths = array(); |
| 45 | |
| 46 | return parent::getVisitor( $context ); |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Compute the depth reached below a selection. Named fragment spreads use |
| 51 | * the fragment's relative depth, computed once; everything else is |
| 52 | * delegated to the stock rule. |
| 53 | * |
| 54 | * @param Node $node The selection node. |
| 55 | * @param int $depth The depth the selection sits at. |
| 56 | * @param int $max_depth The maximum depth seen so far. |
| 57 | * @return int The updated maximum depth. |
| 58 | */ |
| 59 | protected function nodeDepth( Node $node, int $depth = 0, int $max_depth = 0 ): int { |
| 60 | if ( ! $node instanceof FragmentSpreadNode ) { |
| 61 | return parent::nodeDepth( $node, $depth, $max_depth ); |
| 62 | } |
| 63 | |
| 64 | $fragment = $this->getFragment( $node ); |
| 65 | if ( is_null( $fragment ) ) { |
| 66 | return $max_depth; |
| 67 | } |
| 68 | |
| 69 | $name = $fragment->name->value; |
| 70 | if ( ! array_key_exists( $name, $this->fragment_depths ) ) { |
| 71 | // Same cycle guard as the stock rule: a fragment that (transitively) |
| 72 | // spreads itself is reported as exceeding the limit. |
| 73 | if ( isset( $this->calculatedFragments[ $name ] ) ) { |
| 74 | return $this->maxQueryDepth + 1; |
| 75 | } |
| 76 | |
| 77 | $this->calculatedFragments[ $name ] = true; |
| 78 | try { |
| 79 | $this->fragment_depths[ $name ] = $this->fieldDepth( $fragment, 0, self::NO_NESTED_FIELDS ); |
| 80 | } finally { |
| 81 | unset( $this->calculatedFragments[ $name ] ); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | $relative_depth = $this->fragment_depths[ $name ]; |
| 86 | |
| 87 | return self::NO_NESTED_FIELDS === $relative_depth |
| 88 | ? $max_depth |
| 89 | : max( $max_depth, $depth + $relative_depth ); |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Override webonyx's default ("Max query depth should be {max} but |
| 94 | * got {count}."). |
| 95 | * |
| 96 | * @param int $max The configured maximum depth (unused). |
| 97 | * @param int $count The computed query depth (unused). |
| 98 | */ |
| 99 | public static function maxQueryDepthErrorMessage( int $max, int $count ): string { |
| 100 | return 'Maximum query depth exceeded.'; |
| 101 | } |
| 102 | } |
| 103 |