cjoudrey / graphql-schema-linter

Validate GraphQL schema definitions against a set of rules
MIT License
694 stars 62 forks source link

Custom rule sample #279

Open ludovic-pourrat opened 3 years ago

ludovic-pourrat commented 3 years ago

Hello,

I don't find any guidelines or samples to setup custom rules ?

cjoudrey commented 3 years ago

Hello @ludovic-pourrat,

There are some instructions on how to do this in the README: https://github.com/cjoudrey/graphql-schema-linter#customizing-rules

You can also look at this library's rule set to better understand the syntax.

Let me know if you have other questions.

Cheers!

mac2000 commented 3 years ago

Got here with exactly the same question, just in case for anyone looking for a custom rule example

Suppose we have following schema:

type Country {
  id: ID!
  name: String!
}

type City {
  id: ID!
  name: String!
  countryId: ID!
}

Technically schema is valid, but it has code smell

We going to prevent such cases by disallowing fields like whateverId: ID

rules/identifiers-are-connected.js

const { ValidationError } = require('graphql-schema-linter/lib/validation_error')

const camelCaseTest = RegExp('^.+Id$');

function IdentifiersAreConnected(context) {
    return {
    FieldDefinition(node, key, parent, path, ancestors) {
      const fieldName = node.name.value;
      let type = node.type;
      while (type.type) {
        type = type.type;
      }
      if (camelCaseTest.test(fieldName) && type.name.value === 'ID') {
        const parentName = ancestors[ancestors.length - 1].name.value;
        context.reportError(
          new ValidationError(
            'identifiers-are-connected', // <- Naming: Be careful with this one
            `The field \`${parentName}.${fieldName}\` is not connected.`,
            [node]
          )
        );
      }
    },
  };
}

module.exports = {IdentifiersAreConnected} // <- Naming: Be careful with this one

Notes:

And now it is time to run our rule chek:

graphql-schema-linter schema.graphql --custom-rule-paths rules/*.js --rules identifiers-are-connected

And if everything correct you will get desired:

9:3 The field `City.countryId` is not connected.  identifiers-are-connected

Notes:

graphql-schema-linter.config.js

module.exports = {
    // https://github.com/cjoudrey/graphql-schema-linter#built-in-rules
    rules: [
        // 'arguments-have-descriptions',
        'defined-types-are-used',
        'deprecations-have-a-reason',
        'descriptions-are-capitalized',
        'enum-values-all-caps',
        // 'enum-values-have-descriptions',
        // 'enum-values-sorted-alphabetically',
        'fields-are-camel-cased',
        // 'fields-have-descriptions',
        // 'input-object-fields-sorted-alphabetically',
        'input-object-values-are-camel-cased',
        // 'input-object-values-have-descriptions',
        // 'interface-fields-sorted-alphabetically',
        'relay-connection-types-spec',
        'relay-connection-arguments-spec',
        // 'type-fields-sorted-alphabetically',
        'types-are-capitalized',
        // 'types-have-descriptions',
        'identifiers-are-connected', // <- here is our custom rule
    ],
    customRulePaths: [
        'rules/*.js' // <- add everything at once
    ],
    ignore: {
        'defined-types-are-used': [
            'DateTimeOffset',
            'Seconds',
            'Milliseconds',
            'Uri',
            'Guid',
            'Short',
            'UShort',
            'UInt',
            'Long',
            'BigInt',
            'ULong',
            'Byte',
            'SByte',
        ],
        'fields-are-camel-cased': [
            'Query._entities',
            'Query._service',
        ],
        'types-are-capitalized': [
            '_Service'
        ]
    },
    schemaPaths: [
        'schema.graphql'
    ]
}
isha-talegaonkar commented 2 years ago

Hi,

Is there a way to be able to test our custom rules?

cjoudrey commented 2 years ago

Hi,

Is there a way to be able to test our custom rules?

Hi @isha-talegaonkar,

I would recommend digging into the library's test suite you'll be able to find examples of how the library's rules are tested.

Here are two files that could be useful:

If folks would find this useful, we can always export the test helpers so that other developers can use them when testing their custom rules.

mac2000 commented 11 months ago

heh, hello few years later, landed here while trying to figure out is there typescript typings and found this issue :)

for anyone interesting of tests, here is how it may look like for example above:

const { IdentifiersAreConnected } = require('./identifiers-are-connected')
const { expectFailsRule, expectPassesRule } = require('../utils/assertions')

describe('IdentifiersAreConnected', () => {
  it('catches fields that are not connected', () => {
    expectFailsRule(
      IdentifiersAreConnected,
      `
          type Country {
            id: ID!
          }
          type City {
            # Invalid
            countryId: ID!
            # Valid
            country: Country
          }
          interface CityInterface {
            # Invalid
            countryId: ID!
            # Valid
            country: Country
          }
        `,
      [{ message: 'The field `City.countryId` is not connected.' }, { message: 'The field `CityInterface.countryId` is not connected.' }],
    )

    expectPassesRule(
      IdentifiersAreConnected,
      `
        type AddResourceOutputData {
          resourceId: ID
        } 
      `,
    )
  })
})