Skip to content

feat: Allow Custom Directives#815

Open
michael-georgiadis wants to merge 7 commits into
thecodingmachine:masterfrom
michael-georgiadis:feat/custom-directives-v2
Open

feat: Allow Custom Directives#815
michael-georgiadis wants to merge 7 commits into
thecodingmachine:masterfrom
michael-georgiadis:feat/custom-directives-v2

Conversation

@michael-georgiadis

Copy link
Copy Markdown
Contributor

Description

As discussed in #807, the library can support custom defined directives as well. This PR addresses that concern by adding:

  • Discovery — DirectiveClassFinder scans the registered namespaces for classes implementing TypeSystemDirective (cached like ClassFinderTypeMapper). No new configuration: a directive class in a namespace you already addNamespace() is picked up automatically.
  • Registration & validation — DirectiveRegistry now discovers user directives alongside the built-ins (@oneOf, @deprecated), validating each at discovery time and enforcing unique names.
  • Validation split into focused units: AttributeTargetValidator (the #[Attribute(...)] targets cover the declared GraphQL locations) and InterfaceLocationValidator (family interface ⇄ location agreement).
  • Wiring — SchemaFactory injects the class finder into the registry; behavioral directives wrap their resolver at runtime, metadata directives land on each element's astNode->directives.

Directive definition

Defining a directive

#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
final class UppercaseDirective implements BehavioralFieldDirective
{
    public static function definition(): DirectiveDefinition
    {
        return new DirectiveDefinition(name: 'uppercase', locations: [DirectiveLocation::FIELD_DEFINITION]);
    }

    public function applyToField(QueryFieldDescriptor $descriptor, FieldHandlerInterface $next): FieldDefinition|null
    { /* wrap the resolver */ }

Name reservation

  • @oneOf / @deprecated (names we bind): reserved for custom use unless the directive declares builtIn: true to intentionally override the bundled binding.
  • @skip / @include / @specifiedBy (webonyx directives we don't bind): hard-reserved — sourced from Directive::*_NAME constants — so a custom directive can't collide with them at schema build.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.87755% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.29%. Comparing base (53f9d49) to head (a1c40c8).
⚠️ Report is 161 commits behind head on master.

Files with missing lines Patch % Lines
src/Directives/DirectiveRegistry.php 84.61% 4 Missing ⚠️
src/Directives/DirectiveValidator.php 83.33% 1 Missing ⚠️
...rectives/Validation/InterfaceLocationValidator.php 93.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #815      +/-   ##
============================================
- Coverage     95.72%   94.29%   -1.44%     
- Complexity     1773     2059     +286     
============================================
  Files           154      202      +48     
  Lines          4586     5517     +931     
============================================
+ Hits           4390     5202     +812     
- Misses          196      315     +119     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants