Resolving nullable fields with directives - graphql

I'm developing a GraphQL Directive named bind which can be used on fields to resolve into another values.
# This is a simplification of the actual definitions
directive #bind(value: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
input Request {
expr: String! #bind(value: "/v1.0/std/expr")
}
type Response {
message: String
}
type Query {
hello(request: Request): Response
}
It works well if expr is a non-null string. I want my directive to be invoked even if expr is null.
Would you please tell me how?

Related

GraphQL directives with an object array argument

I am trying to define a GraphQL directive which has an array argument which looks like this:
input Data {
someField: String!
anotherField: String!
}
directive #someDirective( args: [Data!]! ) on OBJECT
type SomeType #someDirective( args: [{someField: "blah", anotherField: "blah"}] )
{
empty: String
}
If I change Data to be a type, the GraphQL compiler get the error:
"The type of #someDirective(args:) must be Input Type but got: [Data!]!."
The problem with using input is that there is no validation of the input by the compiler. This compiles fine:
type AnotherType #someDirective( args: [{blah: "blah", blah2: "blah"}] )
{
empty: String
}
Is it possible to define an object array argument for a directive which gets validated by the compiler?

Why directive may not be used on INPUT_FIELD_DEFINITION

I try to set length limit to my graphQl schema via directive
const typeDefs = gql`
directive #length(max: Int!) on FIELD_DEFINITION
type Book {
id: ID
title: String
author: String
pages: Float
}
input PagesInput {
pages: Float #length(max: 10)
}
type Mutation {
changePages(bookId: ID, pagesInput: PagesInput): Book
}
`;
after it I made custom function lengthDirective using apollo instructions.
This function works properly if I set directive to Books type (not to input).
But if I set it to input (where I really need it), I receive an error:
Error: Directive "#length" may not be used on INPUT_FIELD_DEFINITION.
Why it that? How can I set up directive to input type?

Apollo client's codegen adds unwanted "or null" in my types

Apollo client's codegen adds | null in the generated types, and I don't understand why they are there and how to get rid of them.
I see no reason why the API would return an array of null, so I don't want to check in my code weather the oject is null or not everytime.
Offending generated types from apollo codegen:
export interface MusicGenres_musicGenres {
name: string;
}
export interface MusicGenres {
musicGenres: (MusicGenres_musicGenres | null)[];
^^^^^^
WHY ?
}
My Graphql Schema:
type Query {
musicGenres: [MusicGenre]!
}
type MusicGenre {
id: ID!
name: String!
}
Query in my TypeScript code from which are generated the types:
gql`
query MusicGenres {
musicGenres { name }
}
`
In your schema, you have the following field definition:
musicGenres: [MusicGenre]!
This means that while musicGenres will be a list and will itself never be null, any item in the list could be null. If you want to indicate that all items in the list are also non-nullable, your field definition should instead be:
musicGenres: [MusicGenre!]!
See this post for an extended explanation.

Omitting the field in response with GraphQL Apollo

I am using Apollo GraphQL server and directives.
Here is my simple schema. Notice the directive on the token field, User type.
const typeDefs = `
directive #allow(service: String) on FIELD_DEFINITION
type User {
email: String!
pass: String!
... other fields here
token: String #allow(service: "login")
}
type Mutation {
login(email: String!, pass: String!): User
}`;
I would like to return the token field only if the login has been called. Otherwise, I would like to return the User object without the token field, all I could find is throwing an Exception or returning the null in the "token" field.
class SkipDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field, details) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
// If called in context different from "login"
// Here I would like to just "delete" the "token" field
else {
const result = await resolve.apply(this, args);
return result;
}
};
}
}
Ideas?
If a field is requested, it should be returned with either a value matching the field's type or else null. To do otherwise would break the spec.
There is no way you can modify this behavior through a schema directive. A field definition directive can only change runtime behavior by modifying the field's resolver. However, by the time the resolver is called, the selection set has already been determined so it's too late to modify it. Returning null or throwing an error are pretty much the only two options.
You might be able to implement some kind of workaround through either the formatResponse option or a custom plugin. However, because this behavior would break the spec, there's no telling if it wouldn't cause issues with client libraries or other tools.

How to get requested fields inside GraphQL resolver?

I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead

Resources