GraphQL Custom directive enforcing value restrictions

Viewed 119

I need to create a custom directive on INPUT_FIELD_DEFINITION to check if the provided value of the enum isn't being changed to the previous "state" (business logic is that states must go UNAPPROVED - > APPROVED -> CANCELLED -> FULFILLED) but can't quite figure out how to map values in constructor of the enum type.

All my code is available at github

I'm using nextJs backend functionality with neo4j database that generates resolvers for whole schema.

// order Schema

export const order = gql`
  type Order {
    id: ID! @id
    state: OrderState!
    user: User! @relationship(type: "MADE", direction: IN)
    createdAt: DateTime! @timestamp(operations: [CREATE])
    products: [Product!] @relationship(type: "INCLUDE", direction: OUT)
  }

  enum OrderState {
    UNAPPROVED
    APPROVED
    CANCELLED
    FULFILLED
  }
`;

export const extendOrder = gql`
  extend input OrderCreateInput {
    state: OrderState!
  }
`;

I want to create @checkState directive that would check if the updating state is valid

I used as a base example from GraphQL Tools docs but it's with string values. I would much appreciate any help.

1 Answers

Instead of using custom directive I used graphql-middleware lib to create middleware that is triggered only when updateOrders mutation is being used.

Middleware

import { ValidationError } from "apollo-server-micro";
import { IMiddleware } from "graphql-middleware";
import { Order } from "pages/api/graphql";

export const checkStateMiddleware: IMiddleware = {
  Mutation: {
    updateOrders: async (resolve, parent, args, ctx, info) => {
      const { where, update } = args;

      const [existing] = await Order.find({
        ...where,
      });

      const states = ["UNAPPROVED", "APPROVED", "CANCELLED", "FULLFIELD"];

      const currentState = states.indexOf(existing.state);
      const toBeUpdatedState = states.indexOf(update.state);

      if (toBeUpdatedState < currentState) {
        throw new ValidationError("Can't update value with previous state.");
      }

      return resolve(parent, args);
    },
  },
};

then I apply it in pages/api/graphql.ts

//...
import { applyMiddleware } from "graphql-middleware";
//...
const schemaWithMiddleware = applyMiddleware(schema, checkStateMiddleware);

const apolloServer = new ApolloServer({ schema: schemaWithMiddleware });
//...
Related