-
Notifications
You must be signed in to change notification settings - Fork 7
User-defined directives / decorators #123
base: main
Are you sure you want to change the base?
Conversation
| } | ||
| } | ||
|
|
||
| return $directives; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's easy to miss, but this is what the generated code will look like when we add it to each field: https://github.com/mwildehahn/hack-graphql/pull/123/files#diff-bc6416c0810e5e09cdcf817fbe7fc23de76a1884523575597eb7a6b4bc6b9861R41-R51
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend always generating some of the code as part of the PR so that people can see and review the generated code as if it were human-written. Generally speaking it's best to hold generated code to the same high standards as human-written code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally. Do you mean that I should include generated code beyond the code this PR includes currently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I meant that I could not identify by looking at your link that it was a link to lower in this PR. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I'd recommend reviewing this whole file: https://github.com/mwildehahn/hack-graphql/pull/123/files#diff-bc6416c0810e5e09cdcf817fbe7fc23de76a1884523575597eb7a6b4bc6b9861
It contains generated code for object and field-level directives.
src/Codegen/DirectivesFinder.hack
Outdated
| $use_decls = await self::getUseDeclarations($file_path); | ||
|
|
||
| // For each attribute, see if it matches a user-defined directive | ||
| // This requires building the qualified name, which is not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll probably want to put the "use string operations to build the fully qualified name" into a core utility library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good call.
| abstract const type TField as shape( | ||
| 'name' => string, | ||
| 'output_type' => shape('type' => string, ?'needs_await' => bool), | ||
| 'directives' => vec<string>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the spec directives can take arguments so you might want to future proof this and make it dict<string, mixed>. For sure you also don't want to double-up on the directives. And you'll also want key-based lookup instead of C\contains($vec, $thing) which is O(n) linear search.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, these are just strings for the purpose of this prototype. Agree storing as a dict is better.
src/Codegen/DirectivesFinder.hack
Outdated
| return null; | ||
| } | ||
| $leaves = $decl->getClausesx()->toVec(); | ||
| if ($decl is \Facebook\HHAST\NamespaceGroupUseDeclaration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HackAst is really slow and changes rapidly along with the language so a lot of it is dangerous to use. Can you use ReflectionClass::getNamespaceName() for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At FB we ban the use of HackAst for anything that's production-critical, including build systems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think ReflectionClass::getNamespaceName() will work, at least as the code is written currently: we don't know which class to reflect on until we build the fully-qualified attribute name, which requires parsing the use declarations.
I'm hoping the Facts extension can provide a better alternative to using HHAST, but at Slack we're not yet on a version of HHVM which supports Facts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed using HHAST is a bummer. I can play around with ReflectionClass more, but I have to admit I'm not very optimistic about it working.
If developers manually registered their custom directives, then we could use Reflection{Class, Method}::getAttributeClass, which actually does what we want. So maybe that's the interim approach until we support Facts: when creating a new directive, developers register it with the generator:
await GraphQL\Codegen\Generator::forPath(
__DIR__.'/Fixtures',
shape(
'output_directory' => __DIR__.'/gen',
'namespace' => 'Slack\GraphQL\Test\Generated',
'custom_directives' => shape(
'fields' => vec[Directives\HasRole::class],
'objects' => vec[Directives\HasRole::class],
),
),
);That's starting to feel like the cleanest way of doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registering feels totally reasonable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, did that in the latest commit: 355b141
I like where this is headed now. Will work on object directives and introspection next.
This PR is a prototype for how we might support user-defined directives / decorators which can be used to, for example, implement permissions. Specifically:
I still need to implement the following:
Stuff I like:
Stuff I don't like:
var_exportto instantiate the directives with the appropriate args within each GQL type.