Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom rule sample #279

Open
ludovic-pourrat opened this issue Feb 18, 2021 · 5 comments
Open

Custom rule sample #279

ludovic-pourrat opened this issue Feb 18, 2021 · 5 comments

Comments

@ludovic-pourrat
Copy link

Hello,

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

@cjoudrey
Copy link
Owner

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
Copy link

mac2000 commented May 6, 2021

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

@isha-talegaonkar
Copy link

Hi,

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

@cjoudrey
Copy link
Owner

cjoudrey commented Jul 6, 2022

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
Copy link

mac2000 commented Dec 28, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants