graphql-schema-linter icon indicating copy to clipboard operation
graphql-schema-linter copied to clipboard

Custom rule sample

Open ludovic-pourrat opened this issue 4 years ago • 5 comments

Hello,

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

ludovic-pourrat avatar Feb 18 '21 08:02 ludovic-pourrat

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!

cjoudrey avatar Feb 21 '21 18:02 cjoudrey

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:

  • forgive me for code quality it is just plain copy pasta from here and here
  • be very careful with naming, your rule name and exported function name must be the same (filename can be anything)

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:

  • note that it is required not only to instruct linter about custom rules but also to add them to the list of rules to check
  • it is better and easier to have a config file like the one below

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'
    ]
}

mac2000 avatar May 06 '21 08:05 mac2000

Hi,

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

isha-talegaonkar avatar Jul 05 '22 16:07 isha-talegaonkar

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:

  • https://github.com/cjoudrey/graphql-schema-linter/blob/master/test/rules/types_have_descriptions.js
  • https://github.com/cjoudrey/graphql-schema-linter/blob/master/test/assertions.js

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.

cjoudrey avatar Jul 06 '22 19:07 cjoudrey

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
        } 
      `,
    )
  })
})

mac2000 avatar Dec 28 '23 18:12 mac2000