Allow one to associate Directives to Types via the Perl API
The GraphQL::Directive class makes it easy to programmatically create new Directives and add them to your Schema. I would like to make it equally easy to associate these Directives with GraphQL::Type classes that you might use to programmatically create the various types. Although you can also do this with the GraphQL Schema Language I believe it will be very useful to also be able to add them via the Perl API (by this I mean the GraphQL::Type API for example). There are legitimate use cases where one wishes to generate a Schema programmatically (for example by introspecting a DBIx::Class representation of a database). Additionally one readily finds examples of this support in other languages, which is a good example of the fact some programmers find this ability useful. For example: https://www.npmjs.com/package/graphql-custom-directive
- Please note this ticket is a proposal only to be able to add Directives to types via the Perl API as meta data. It is not a proposal on how we might have a server side API to handle those Directives. That will be a separate ticket.
Here's only possible approach for discussion
use GraphQL::Directive;
my $directive = GraphQL::Directive->new(
name => 'IsAuthorized',
description => 'Is the user authorized to see this?',
locations => ['FIELD'],
);
use GraphQL::Type::Object;
my $type = GraphQL::Type::Object->new(
name => 'Object',
fields => {
field_name => {
type => $scalar_type,
directives => [
{
name => 'isAuthorized',
},
],
},
},
);
Basically we'd probably have a new Role for storing directives, associate that with all the relevant classes and then add the code in the places we build the AST or Doc.
As discussed, please give actual queries your FE folks expect to run.
Here's a use case. My front end team does some validations on forms that are a mix of fully client side validation (like number of characters) and 'queries to a back end server' validation. For example when display a form for a client to create an account we ask for their name and form them to choose a unique username. For three fields (first_name, last_name and user_name). Client side we validate that those fields have the proper length mins and maxes and check for disallowed characters etc. That validation happens dynamically (via javascript when a client exists the field, before hitting the 'submit button'. That way we give them immediate feedback (and we avoid a POST to our already overloaded server).
This also happens for many server side validation requirements. So when a client types in a user name and completes the field, the FE team issues a GET to the 'all user_names cache DB' (which is a stand alone Redis API that syncs with the production DB) to make sure the chosen name is actually unique. This happened before the client clicks submit and sends all the form data to the main application server. This way the client gets immediate error feedback on each field as its completed rather than waiting for a big list of errors after clicking 'submit'. This also lowers load on on production server. BTW this query could in theory be a POST to a GraphQL query if we do this right :)
My FE team is requesting that the GraphQL schema we generate contains Directive tags for things like 'this is a unique field' that way they can generate the correct validations without having to hardcode that information. They can just write code that reads the type info and gets from the directives things like 'Unique' and other things like Max and Min lengths, etc. Then they can avoid hardcoding in the client side and we can be more agile and make changes server side that they don't need to rewrite code for. For example they would like a type info that looks like this:
type User {
first: String! @length(max: 50)
last: String! @length(max: 50)
username: String! @length(max: 50, min: 3) @unique
}
So then they would have javascript that inspects that and builds client side validation code that automatically enforces the rules, instead of hard coding all those validation on a per form basis. These directives would be generated server side by the DBIC converter, which would need an update to add them basic on inspecting the Schema.
Here's what the it might look like on the Perl side, to create a type like the one above:
use GraphQL::Directive;
my $directive_length = GraphQL::Directive->new(
name => 'length',
args => { max => { type => 'Int!'}, min => {type=>'Int!'} },
description => 'allowed lengths for the string',
locations => ['FIELD'],
);
my $directive_unique = GraphQL::Directive->new(
name => 'unique',
description => 'Must be unique in the DB',
locations => ['FIELD'],
);
# Both $directive_unique and $directive_length added to $graphql_schema
use GraphQL::Type::Object;
my $user_type = GraphQL::Type::Object->new(
name => 'User',
fields => {
first => {
type => "String!",
directives => [
{ name => 'length', arguments => { max=>50} },
],
},
last => {
type => "String!",
directives => [
{ name => 'length', arguments => { max=>50} },
],
},
user_name => {
type => "String!",
directives => [
{ name => 'length', arguments => { max=>50, min=>3} },
{ name => 'unique' },
],
},
}
);
To avoid doubt, this does add clarity. However, it still doesn't show any actual queries, which would be run against the API. Do I understand right that the verification of uniqueness would be run on a different service and not in the GraphQL service itself?
Yes for my purposes the FE has its own API for the 'unique' example. . However I hope to be able to convince them to switch to GraphQL and we can kill that stand alone API. For example I think the uniqueness check could be done in Graphql with something like:
(what the FE teams POSTs)
{
user(username:"example_username") {
id
}
}
(What the GraphQL API returns when the name isn't unique)
{
"data": {
"user": [
{
"id": "100"
},
]
}
}
(What the GraphQL API returns when the name IS unique)
{
"data": {
"user": [
]
}
}
There's probably a better API we could imagine but this is one simple
That sounds like a security hole since bad people could use the API (GraphQL or otherwise) to figure out what usernames to try brute-forcing.
Certainly the idea of having programmatically created types, and also SDL ones, have these directives, is a good idea. What is done with that afterwards can be a problem for another day. I will ponder.
@mohawk2 I think that could be another reason the FE team wanted its own server for some of these checks. They have a type of authorization on that server that uses a single use key or something so that only they are supposed to be able to hit it. But like I said its just something off the top of my head.